文章目录
- The First Week
- 一、前言
- 二、算法
- 1.逆序对
- <1>(2024牛客国庆集训派对day2 I)
- 2.图论
- <1>(2024牛客国庆集训派对day2 F)
- 3. 二分
- <1>(AcWing 102. 最佳牛围栏)
- <2>(2024牛客国庆集训派对day2 J)
- 4. dijkstra
- <1>(牛客小白月赛102 D)
- 5.BFS算法
- <1>(Required Length)
- 三、总结
The First Week
长江后浪推前浪, 浮事新人换旧人。 ————刘斧
一、前言
二、算法
1.逆序对
存在1<=i<j<=n,A[I]>A[j]的有序对叫作逆序对。
一些简单朴素算法的时间复杂度都是O(n*n),包括插入排序冒泡排序等等。
归并排序
- 二分一直二分到只有一个元素为止
- 可以想象在二分时,逆序对有以上三种结果,全在左边,全在右边,一左一右
- 全在左右的都不容易计算,所以继续归并排序
- 如果在二分到仅剩一个元素时进行排序,那么当回来计算归并排序时,左右俩边应当是分别排序好的数组
- 开始寻找逆序对个数,分别把左右俩个区间的最小值设置成i,j。当找到一个a[i]>a[j]时,前半部分i后面的元素都将与j形成逆序对。
- 剩下的没有被扫的元素也得按顺序存入数组b,在最后赋值给a。
int ans = 0;
//储存逆序对个数
void gbsort(int l,int r,int a[]) {
int mid = (l+r)/2;
if(l == r) return ;
//二分到只有一个就终止
gbsort(l,mid,a);
gbsort(mid+1,r,a);
//先计算俩边的逆序对
int i = l,j = mid+1;
int k = 0;
int t = l;
while(i <= mid && j <= r) {
if(a[i] > a[j]) {
ans += (mid+1-i);
b[t++] = a[j++];
}
else b[t++] = a[i++];
}
while(i <= mid) b[t++] = a[i++];
//右边已经跑完了,左边的有剩余
while(j <= r) b[t++] = a[j++];
for (int i = l; i <= r; i++) {
a[i] = b[i];
}//必须排序
return ;
}
树状数组
<1>(2024牛客国庆集训派对day2 I)
依然不是我写的代码,树状数组没太学明白,基本都是抄模版。over。
题解:
给定一个数组a,包括不重复的1到n的数字,可以给任意多个数字加上1,求最小的逆序对数目。
先求出逆序对的个数,然后查找每个数字的位置,如果i+1的位置在i的位置的前面,就把i变成i+1,并跳过i+1的这一个操作。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n;
int b[200005];
int t[200005]={0};
typedef struct node{
int val,ind;
}Node;
Node stu[200005];
int Rank[200005];
int lowbit(int x){
return x&(-x);
}
void add(int pos){
for(int i=pos;i<=n;i+=lowbit(i)) t[i]+=1;
}
int ask(int pos){
int ans = 0;
for(int i=pos;i;i-=lowbit(i)) ans += t[i];
return ans;
}
int cmp(Node a,Node b){
if(a.val == b.val) return a.ind<b.ind;
return a.val<b.val;
}
signed main(){
int ans = 0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>stu[i].val;
b[stu[i].val]=i;
stu[i].ind = i;
}
sort(stu+1,stu+n+1,cmp);
for(int i=1;i<=n;i++) Rank[stu[i].ind] = i;
for(int i=1;i<=n;i++){
int pos = Rank[i];
ans += ask(n)-ask(pos);
add(pos);
}
//cout<<ans;
for(int i=1;i<n;i++){
if(b[i+1]<b[i]) ans--,i++;
}
cout<<ans;
return 0;
}
2.图论
<1>(2024牛客国庆集训派对day2 F)
这题其实不是图论,还是博弈论,但对我们队来说挺崩溃的吧,就还是归在图论板块里了。
Alice和Bob又要玩游戏了…
题解:
给定n个节点m条线的图,可以选择删去一个联通块(不能自成环),或者删去一条线。
找到所有的联通块个数(包括单独点)和成环的联通块(判断环内线段的数目)…反正是一个繁琐且容易出错的思考过程,最后对这个个数判断奇偶。
但是!删边的时候边的数目-1,删联通块的时候,点的个数比边的个数多一,那么就不形成环,所以每次进行操作奇偶都会改变,直接判断点和边的和的奇偶性即可。
代码:
正解的代码真的很简单,我就不放了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=110;
int n,m;
vector<int>a[N];
vector<int>ans;
int dev[N];
bool st[N];
bool vis[N];
int k=0;
int an=1;
int res=0;
bool cmp(int x,int y){
return x>y;
}
vector<int>b;
void dfs2(int x,int fa){
// cout<<x<<endl;
b.push_back(x);
vis[x]=1;
if(a[x].size()==1&&a[x][0]==fa) {
return;
}
for(int i=0;i<a[x].size();i++){
if(a[x][i]==fa) continue;
if(vis[a[x][i]]) {
k=1;
continue;
}
an++;
dfs2(a[x][i],x);
}
}
int findf(int x){
int sumn=0;
for(int i=0;i<b.size();i++){
if(!st[b[i]]){
// cout<<b[i]<<endl;
for(int j=0;j<a[b[i]].size();j++){
if(!st[a[b[i]][j]]) sumn++;
}
}
}
return sumn/2;
}
int find(int x){
int num=0;
queue<int>q;
for(int i=0;i<b.size();i++){
if(dev[b[i]]==1){
q.push(b[i]);
st[b[i]]=1;
num++;
}
}
while(q.size()){
int xx=q.front();
q.pop();
for(int i=0;i<a[xx].size();i++){
dev[a[xx][i]]--;
if(dev[a[xx][i]]==1) {
num++;
st[a[xx][i]]=1;
q.push(a[xx][i]);
}
}
}
// cout<<findf(x)<<" "<<an-num<<endl;
return findf(x)-an+num;
}
void solve(){
cin>>n>>m;
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
a[x].push_back(y);
a[y].push_back(x);
dev[x]++;
dev[y]++;
}
int tip=0;
for(int i=1;i<=n;i++){
if(!vis[i]){
dfs2(i,0);
// cout<<i<<" "<<an<<endl;
if(!k){
b.clear();
ans.push_back(an);
}
else {
// cout<<find(i)<<endl;
res+=find(i);
tip=1;
}
k=0;
an=1;
}
}
// cout<<res<<endl;
if((ans.size()+res)%2==0) cout<<"Bob"<<endl;
else cout<<"Alice"<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
while(t--){
solve();
}
return 0;
}
3. 二分
<1>(AcWing 102. 最佳牛围栏)
题解:
N块土地,要求至少F块地的牛的平均值可能的最大值是多少。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define endl "\n"
#define PII pair<int,int>
int n,m;
const int N = 1e5+10;
int c[N],sum[N];
double l,r;
bool check(double x) {
for (int i = 1; i <= n; i++) {
sum[i] = sum[i-1]+c[i]-x;
}
double mi = 0;
for (int i = 0, j = m;j <= n; j++,i++) {
mi = min(mi,sum[i]);
if(sum[j]-mi >= 0) return true;
}return false;
}
void solve() {
cin >> n >> m;
l = 0,r = 0;
for (int i = 1;i <= n; i++) {
cin >> c[i];
r = max(r,(double)c[i]);
}
while(r-l > 1e-5) {
double mid = (l+r)/2;
if(check(mid)) l = mid;
else r = mid;
}cout << r*1000 << endl;
return ;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin >> t;
while(t--) {
solve();
}
return 0;
}
<2>(2024牛客国庆集训派对day2 J)
题解:
给定数组a,b,矩阵对应位置大小是a[i]+b[j],要求取出一个行不小于x,列不小于y的矩阵的平均值最大。推算可发现应当要分别找出a,b中连续区间平均值最大的区间。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
int n,m,z,y;
const int N = 1e5+10;
int a[N],b[N];
double sum[N];
double ans;
bool check(double x,int c[],int jx,int n) {
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i-1]+c[i]-x;
}
double mi = 1e9;
for (int i = 0,j = jx; j <= n; i++,j++) {
mi = min(mi,sum[i]);
if(sum[j]-mi >= 0) return true;
}return false;
}
void solve() {
cin >> n >> m >> z >> y;
double mia=0,maa=0,mib=0,mab=0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
maa=max(maa,(double)a[i]);
}
while(maa-mia > 1e-8) {
double mid = (mia+maa)/2;
if(check(mid,a,z,n)) mia = mid;
else maa = mid;
}
for (int i = 1; i <= m; i++) {
cin >> b[i];
mab=max(mab,(double)b[i]);
}
while(mab-mib > 1e-8) {
double mid = (mib+mab)/2;
if(check(mid,b,y,m)) mib = mid;
else mab = mid;
}
ans = maa+mab;
printf("%0.10Lf",ans);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t = 1;
//cin >> t;
while(t--) {
solve();
}
return 0;
}
4. dijkstra
我真的会疯,反正思路还行,dij+dp的感觉,但写起来我真的是框框改。
<1>(牛客小白月赛102 D)
题解:
tip:花费的时间不要看作边权,当作点权处理。
由于k的范围比较小,在dij的时候记录已经有几次不休息,然后把这次休息和不休息的俩种状态都放进去,如果已经k次不休息,这次必须休息,传入的时候把不休息次数的状态也传入。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int,int> PII;
const int INF = 1e18;
//不可以写0x3f,这是63,要么就多写几个3f
const int N = 1e6;
int n,m,k;
void solve() {
// init();
int a[N];
cin >> n >> m >> k;
vector<int>edge[n+10];
vector<vector<int>>dist(k+10,vector<int>(n+10,INF));
vector<vector<int>>vis(k+10,vector<int>(n+10,0));
//不可以在外面开数组范围N,会内存超限
//好好学一下怎么开二维数组的大小
// int dist[k+1][N+1],vis[][2*N];
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 0; i < m; i++) {
int u,v;
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
int x = 1;
priority_queue<pair<int,PII>,vector<pair<int,PII>>,greater<pair<int,PII>>>pq;
//因为我确实不太会放三个数据,所以就这样放吧
if(k >= 1) {
dist[1][1] = 1;
pq.push({1,{1,1}});
}//如果是0的话就不能放,因为每次都得休息
dist[0][x] = a[1];
pq.push({a[1],{1,0}});
while(!pq.empty()) {
x = pq.top().second.first;
int y = pq.top().second.second;
int z = pq.top().first;
pq.pop();
if(vis[y][x]) continue;
// cout << x << ' ' <<dist[0][x] << endl;
vis[y][x] = 1;
for (int i = 0; i < edge[x].size(); i++) {
int next = edge[x][i];
if(y < k) {
if(dist[y+1][next] > z+1) {pq.push({z+1,{next,y+1}});
dist[y+1][next] = min(dist[y+1][next],z+1);}
}//这次休息
if(dist[0][next] > z+a[next]) {pq.push({z+a[next],{next,0}});
dist[0][next] = min(dist[0][next],z+a[next]); }//这次不休息
}
}
int ans = 1e18;
// cout << dist[]
for (int i = 0; i<= k; i++)
ans = min(ans,dist[i][n]);
//已经跑到第n次,休息0到k次的最小距离
cout << ans << endl;
// cout << "DA" << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t = 1;
cin >> t;
while(t--) solve();
return 0;
}
5.BFS算法
<1>(Required Length)
题解:
可以想象原来的数字x,给它分别乘上各个位数,构成了第二层,再继续往下推,直到有任何一个的位数满足要求,就可以返回此时的层数,也就是操作次数了。注意不要多次对相同的数字操作,因为它会有很多相同的数字,可以节约时间。
代码:
#include <iostream>
#include <set>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<string.h>
#include<map>
#include<math.h>
#include<stack>
using namespace std;
#define int unsigned long long
int n,x;
vector<int>a[30];
bool st[30];
int ans;
int ms = 100;
typedef pair<int,int> PII;
map<int,bool>mp;
int calc(int x) {
int res = 0;
while(x) {
res++;
x = x/10;
}
return res;
}
int js(int x) {
int res = 0;
while(x) {
res = max(res,x%10);
x = x/10;
}
return res;
}
void bfs() {
int res = 0;
queue<PII>q;
q.push({0,x});
while(q.size()) {
int a = q.front().first;
int b = q.front().second;
q.pop();
if(mp[b]) continue;
mp[b] = true;
if(calc(b) == n) {
cout << a << endl;
return ;
}
vector<int>ab;
int xy = b;
while(xy) {
ab.push_back(xy%10);
xy = xy/10;
}
for (auto x : ab) {
q.push({a+1,b*x});
}
}
return ;
}
void solve() {
cin >> n >> x;
ans = 0;
// cout << calc(x) << ' ' << js(x) << endl;
if(calc(x) > n || js(x) == 1) {
cout << -1 << endl;
return ;
}
else if(calc(x) == n) {
cout << 0 << endl;
return ;
}
else {
ans = 0;
bfs();
// cout << ms << endl;
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
// cin >> t;
while(t--){
solve();
}
return 0;
}
三、总结
累了,不想总结,先这么着吧,题都写不完了已经。
哦不过我写的题还是不止这些的,这些都是我理解了很久的题,就放出来看看。