本来明天有活动,晚上 VP 了一场 abc 找找手感,结果活动取消了(。
额,算了一下,大概是 rk.730 左右,打了就是上分局。
A - Triple Four
按照题目模拟即可。
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=105;
int n,a[N];
int main(){cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i+2<=n;i++){if(a[i]==a[i+1]&&a[i+1]==a[i+2]){printf("Yes\n");return 0;}}printf("No\n");return 0;
}
时间复杂度 \(O(n)\)。
B - Card Pile
按照题目模拟即可。
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=305;
int n,stk[N],top;
int main(){cin>>n;for(int i=1;i<=n;i++)stk[++top]=0;for(int i=1;i<=n;i++){int o,x;cin>>o;if(o==1){cin>>x;stk[++top]=x;}else{cout<<stk[top--]<<endl;}}return 0;
}
时间复杂度 \(O(n)\)。
C - Buy Balls
扫码特判。
假设黑球中选择了 \(a\) 个数,白球中选了 \(b\) 个数(\(a>b\))。则最优方案一定是黑球中最大的 \(a\) 个和白球中最大的 \(b\) 个,所以我们按照从大到小的顺序排序后,取出的分别是两个序列的前缀。
假设 \(k\) 为黑球中排序后最后一个正数的位置,显然,如果选择了 \(k\) 这个位置之后的球,方案总权值会越来越小,所以尽量不选,除非 \(b>k\)(这种情况必须满足要求)。
所以我们排序,求出前缀和和 \(k\) 后,枚举 \(b\),若 \(b\le k\),则选择黑球中前 \(k\) 个和白球中前 \(b\) 个。否则,选择黑球和白球中的前 \(b\) 个。
需要注意,白球可以选择 \(0\) 个。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=2e5+10;
ll n,m,a[N],b[N],ans,sum,res;
int k;
bool cmp(ll a,ll b){return a>b;
}
int main(){scanf("%lld %lld",&n,&m);for(int i=1;i<=n;i++)scanf("%lld",a+i);for(int i=1;i<=m;i++)scanf("%lld",b+i);sort(a+1,a+1+n,cmp);sort(b+1,b+1+m,cmp);for(int i=1;i<=n;i++)a[i]+=a[i-1];for(int i=1;i<=m;i++)b[i]+=b[i-1];for(int i=2;i<=n;i++)if(a[k]<a[i])k=i;ans=a[k];for(int i=1;i<=min(n,m);i++)ans=max(ans,b[i]+a[max(k,i)]);printf("%lld\n",ans);return 0;
}
时间复杂度 \(O(n)\)。
D - Minimum XOR Path
D 在 C 之前过的。
注意到 \(n\le 10\),“每个点不重复经过”,考虑搜索。
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=15;
typedef long long ll;
ll w[N][N],ans=2e18;
int n,m,mk[N];
void dfs(int x,ll sum){if(x==n){ans=min(ans,sum);return;}for(int i=1;i<=n;i++){if(!mk[i]&&w[x][i]!=-1){mk[i]=1,dfs(i,sum^w[x][i]),mk[i]=0;}}return;
}
int main(){scanf("%d %d",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j]=-1;for(int i=1;i<=m;i++){int u,v;ll val;scanf("%d %d %lld",&u,&v,&val);w[u][v]=w[v][u]=val;}mk[1]=1;dfs(1,0ll);cout<<ans<<endl;return 0;
}
时间复杂度 \(O(n!)\)。
E - Min of Restricted Sum
嗯嗯嗯,这题就有意思了。
位运算题经典套路拆位。因为各个位之间互相运算不影响,所以我们对 \(A_i,Z_i\) 的每一位分开讨论。让 \(\sum A_i\) 最小,就是让每一位中,\(1\) 的个数最少。
把这个东西简化成子问题,就是给 \(n\) 个 \(01\) 变量 \(A_i\),给定 \(m\) 对关系,每个关系形如 \(A_x\) 和 \(A_y\) 相等/不相等。求得一种方案,最小化变量 \(A\) 中 \(1\) 的个数。
看起来有点像扩展域并查集?我们考虑将所有变量分为两个集合,两个变量相等划分在同一集合,两个变量不相等划分在不同集合。然后将较小集合中的所有变量赋值为 \(1\),另一个集合中的元素赋值为 \(0\)。
然后就做完了?为了方便处理,我们考虑二分图染色,并维护左部点和右部点集合(可以用 vector
)。注意到二分图不一定联通,所以我们对每一个联通块都进行上述算法,如果有一个二分图不是联通块,则无解(同理,若有一位无解,则整个问题无解)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#define pii pair<int,int>
#define mp make_pair
using namespace std;
const int N=2e5+10;
int n,m,x[N],y[N],z[N],a[N],col[N];
vector<int>u,v;
vector<pii>G[N];
void add(int now,int nxt,int val){G[now].push_back(mp(nxt,val));
}
bool dfs(int now,int co){col[now]=co;if(co==1)u.push_back(now);else v.push_back(now);for(auto [nxt,val]:G[now]){if(!col[nxt]){if(val&&dfs(nxt,3-co))return 1;else if(!val&&dfs(nxt,co))return 1;}else{if(val&&col[nxt]==col[now])return 1;else if(!val&&col[nxt]!=col[now])return 1;}}return 0;
}
bool solve(int k){for(int i=1;i<=m;i++){int c=((z[i]>>k)&1);add(x[i],y[i],c);add(y[i],x[i],c);}for(int i=1;i<=n;i++){if(!col[i]){if(dfs(i,1))return 1;else{if(u.size()>v.size())swap(u,v);for(auto p:u)a[p]|=(1<<k);u.clear(),v.clear();}}}for(int i=1;i<=n;i++)G[i].clear(),col[i]=0;return 0;
}
int main(){scanf("%d %d",&n,&m);for(int i=1;i<=m;i++)scanf("%d %d %d",x+i,y+i,z+i);for(int k=0;k<=30;k++){if(solve(k)){printf("-1\n");return 0;}}for(int i=1;i<=n;i++)printf("%d ",a[i]);return 0;
}
时间复杂度 \(O(n\log V)\),其中 \(V\) 是值域。
F - Rotated Inversions
看上去很难,实际上很简单的问题。下文称状态 \(x\) 为当 \(k=x\) 时的 \(B\)。
首先 \(x=0\) 的情况可以暴力求。然后对于任意 \(x\ge 1\),我们求出状态 \(x\) 相对于状态 \(x-1\) 的贡献。
如果我们不看 \(A_i+x\bmod m=0\) 的这些位置 \(i\),则容易发现其他数对没有产生任何逆序对贡献(因为相对之间的大小没有改变)。而这些位置 \(i\) 之间也不会产生贡献。
我们考虑这些位置在状态 \(x-1\) 中的逆序对贡献和 \(x\) 中的逆序对贡献,减去前者加上后者即为所求。
因为 \(A_i+x\bmod m=0\),所以 \(A_i+x-1\bmod m=m-1\),即 \(i\) 在状态 \(x-1\) 是最大的数,所以 \(i\) 前面的数不可能和他构成逆序对,而 \(i\) 后面不和它相等的一定会构成逆序对(因为此时 \(B_i\) 最大)。同理可知,因为在状态 \(x\) 中,位置 \(i\) 是值最小的位置,所以它后面的任何数不可能和他构成逆序对,而 \(i\) 前不和它相等的一定会构成逆序对。
所以我们只需要求出每个数前后各有多少数和他不相等即可,用一个桶即可做到 \(O(n)\)。初始状态的逆序对数可以通过树状数组来求。
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=2e5+10;
typedef long long ll;
ll n,m,ans[N],t[N],a[N],c[N];
void add(int x,int v){x++;for(;x<=m+1;x+=(x&-x))c[x]+=v;return;
}
ll ask(int x){ll cnt=0;x++;for(;x>0;x-=(x&-x))cnt+=c[x];return cnt;
}
int main(){scanf("%lld %lld",&n,&m);for(int i=1;i<=n;i++){scanf("%lld",a+i);ans[0]+=i-1-ask(a[i]);add(a[i],1);}for(int i=1;i<=n;i++)ans[m-a[i]]+=i-1-t[a[i]];for(int i=0;i<=m;i++)t[i]=0;for(int i=n;i>=1;i--)ans[m-a[i]]-=n-i-t[a[i]];printf("%lld\n",ans[0]);for(int i=1;i<m;i++){ans[i]+=ans[i-1];printf("%lld\n",ans[i]);}return 0;
}
时间复杂度 \(O(n\log n)\)。
G - Flip Row or Col
未来有机会补吧。
好菜啊。