Competition Set - Codeforces
这里记录的是这个账号的比赛情况。
Codeforces Round 942 (Div. 1)
Solved:6/8,AB1B2CDE1
2645->2802
A
题意:现有 \(a_i\) 张写有 \(i\) 的卡片和 \(k\) 张空卡片,可以在每张空卡片上写 \(1\cdots n\) 的数字,然后把所有卡片排成一列。问最后的排列中最多有多少个子区间是 \(1,2,\cdots,n\) 的排列。
题解;设有 \(x\) 种卡片的出现次数最少,次数为 \(y\),则最大值是 \(ny-x+1\)。显然策略应该是每次写下一种当前最少的数。二分最后的最小值即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T,n;ll k,a[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%lld",a+i);
sort(a+1,a+n+1);ll L=0,R=2e12,res=0;
while(L<=R){
ll mid=(L+R)>>1,num=0;
for(int i=1;i<=n;i++)
if(a[i]<=mid)num+=mid-a[i];
if(num<=k)res=mid,L=mid+1;else R=mid-1;
}
for(int i=1;i<=n;i++)
if(a[i]<=res)k-=(res-a[i]),a[i]=res;
for(int i=1;i<=k;i++)++a[i];sort(a+1,a+n+1);
int cnt=1;while(cnt<n&&a[cnt]==a[cnt+1])++cnt;
printf("%lld\n",n*a[1]-cnt+1);
}
return 0;
}
B1
题意:给定 \(n,m\),求有多少对 \(1\le a\le n,1\le b \le m\),满足 \(a+b\mid b\cdot \gcd(a,b)\)。
题解:设 \(\gcd(a,b)=d,a=dx,b=dy\),则 \(dy\mid x+y\),\(y\mid x\),而 \((x,y)=1\),所以 \(y=1\),也就有 \(b\mid a\)。进一步,\(a\equiv -b\pmod {b^2}\)。于是直接枚举 \(b\) 计算即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,m;long long ans;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);ans=0;
for(int b=1;b<=m;b++)
ans+=(n+b)/(1ll*b*b)-(b==1);
printf("%lld\n",ans);
}
return 0;
}
B2
题意:给定 \(n,m\),求有多少对 \(1\le a\le n,1\le b \le m\),满足 \(b\cdot \gcd(a,b) \mid a+b\)。
题解:设 \(\gcd(a,b)=d,a=dx,b=dy\),则 \(x+y\mid dy\)。约掉右边的 \(y\),可以发现 \(x+y\mid d\),则 \(x,y\le x+y\le d\)。那么 \(x\le \sqrt n,y\le \sqrt m\)。直接枚举 \(x,y\),根据 \(d\) 的范围计算即可。复杂度就是 \(O(\sqrt {nm})\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,m;long long ans;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);ans=0;
for(int x=1;x*x<=n;x++)
for(int y=1;y*y<=m;y++){
if(__gcd(x,y)!=1)continue;
ans+=min(n/x,m/y)/(x+y);
}
printf("%lld\n",ans);
}
return 0;
}
C
题意:将一个数组变成它求树状数组 \(\bmod 998244353\) 之后的结果,称为一次变换。给定一个数组进行 \(k\) 次变换之后的结果,构造原数组。
题解:考虑初始数组中的第 \(i\) 个数对各个位置的贡献,会发现贡献到 \(i\to i+\text{lowbit}(i)\to \cdots\) 这样一条链上,每次可以沿着这条链走任意多步。那么贡献到走 \(m\) 步之后的方案数等价于 \(k\) 个非负整数和为 \(m\) 的方案数,也就是 \(C_{m+k-1}^{m}\)。那么直接枚举点对点的贡献即可,预处理组合数后,复杂度是 \(O(n\log n)\) 的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,P=998244353;
int qpow(int a,int b=P-2){
int c=1;
for(;b;b>>=1,a=1ll*a*a%P)
if(b&1)c=1ll*c*a%P;
return c;
}
int T,n,k,C[N],a[N],b[N];
int main(){
scanf("%d",&T);C[0]=1;
while(T--){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",b+i);
for(int i=1;i<=30;i++)
C[i]=1ll*C[i-1]*qpow(i)%P*(k+i-1)%P;
for(int i=1;i<=n;i++){
a[i]=b[i];printf("%d ",a[i]);
for(int j=0,x=i;x<=n;j++,x+=x&-x)
b[x]=(b[x]-1ll*a[i]*C[j]%P+P)%P;
}
printf("\n");
}
return 0;
}
D
题意:给定一个 \([1,m]\) 内长为 \(n\) 的数列 \(a\),和一个 \([1,m]\) 内长为 \(m\) 的序列 \(b\)。每次可以选择一个集合 \(S\),然后对所有 \(i\in S\),将 \(a_i\) 变为 \(b_{a_i}\)。求至少要多少次操作才能使序列 \(a\) 单调不减,或报告无解。
题解:显然 \(i\to b_i\) 构成一个内向树森林,问题可以重新叙述为:每个数 \(a_i\) 可以在这个森林上走若干步,求使得 \(a\) 单调不减时,操作次数最大值的最小值。考虑二分答案,维护一个指针表示上一个数的最小值,不断尝试增加,那问题就转化为判断从 \(u\) 走 \(x\) 步是否会经过 \(v\)。这只要对基环树做一些基本的处理就可以了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int T,n,m,a[N],b[N],ans;
int subt[N],dep[N],dfn[N],ed[N],dcnt,cyc[N],rk[N],len[N],num;
int ind[N],vis[N];queue<int> Q;vector<int> G[N];
void dfs(int u){
dfn[u]=++dcnt;for(int v:G[u])
dep[v]=dep[u]+1,subt[v]=subt[u],dfs(v);
ed[u]=dcnt;
}
bool check(int u,int v,int x){
// for(int w=u,i=0;i<=x;i++,w=b[w])if(w==v)return true;return false;
int tu=subt[u],tv=subt[v];
if(cyc[tu]!=cyc[tv])return false;
if(!cyc[v]){
if(dfn[v]>dfn[u]||ed[v]<dfn[u])return false;
if(dep[u]-dep[v]>x)return false;return true;
}
int p=dep[u]+(rk[tv]-rk[tu]+len[cyc[tu]])%len[cyc[tu]];
return p<=x;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
subt[i]=dep[i]=dfn[i]=ed[i]=cyc[i]=rk[i]=len[i]=0;
ind[i]=vis[i]=0;G[i].clear();
}ans=num=dcnt=0;
for(int i=1;i<=n;i++)scanf("%d",a+i);
for(int i=1;i<=m;i++)
scanf("%d",b+i),++ind[b[i]];
for(int i=1;i<=m;i++)if(!ind[i])Q.push(i);
while(!Q.empty()){
int u=Q.front();Q.pop();vis[u]=1;
if(!--ind[b[u]])Q.push(b[u]);
}
for(int u=1;u<=m;u++)if(!vis[u]){
cyc[u]=++num;len[num]=1;vis[u]=1;
for(int x=b[u];x!=u;x=b[x])
vis[x]=1,cyc[x]=num,rk[x]=len[num]++;
}
for(int i=1;i<=m;i++)if(!cyc[i])G[b[i]].push_back(i);
for(int i=1;i<=m;i++)if(cyc[i])subt[i]=i,dfs(i);
// for(int i=1;i<=m;i++)
// printf("%d %d %d %d\n",subt[i],dfn[i],dep[i],cyc[i],rk[i]);
int L=0,R=m,ans=-1;
while(L<=R){
int mid=(L+R)>>1,now=1;//printf("%d:",mid);
for(int i=1;i<=n;i++){
while(now<=m&&!check(a[i],now,mid))++now;
//printf("%d ",now);
}//printf("\n");
if(now<=m)ans=mid,R=mid-1;else L=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
E
题意:给定 \(n,m,b_0\),考虑所有在 \([1,m]\) 内,长为 \(n\) 的序列 \(a_1,\cdots,a_n\),求有多少个这样的序列 \(a\),使得存在一个非负整数序列 \(b\),使得对 \(1\le i\le n\),有 \(b_i\neq a_i,|b_i-b_{i-1}|=1\)。
题解:考虑固定的 \(a\) 如何构造 \(b\)。显然的贪心策略是让每个 \(b\) 都尽可能的大。那么就可以设 \(dp_{i,j}\) 表示按照上述方法构造出的 \(b_i=j\) 的方案数。转移是 \(dp_{i-1,j}\to dp_{i,j-1},(m-1)dp_{i-1,j}\to dp_{i,j+1}\),答案是 \(\sum_{i=1}^{n}dp_{i,m}m^{n-i}+\sum_{j=0}^{m}dp_{n,j}\)。这样暴力的复杂度是 \(O(nm)\) 的。
这个 DP 是格路计数的形式。观察一下模型,要从 \((0,b_0)\) 开始走,每次往右上方走一步或者往右下方走一步,求(除端点)不经过 \(y=-1\) 和 \(y=m\) 到达 \((\le n,m)\) 和 \((n,\lt m)\) 的方案数。那么这就是一个反射容斥的模型,可以旋转 \(45\degree\) 后平移转化为反射容斥的模板。我们总共有 \(n+m\) 个 \(\le n\) 步的反射容斥,复杂度就是 \(O(\frac{n^2}{m}+m)\)。可以结合前面的暴力进行根号分治,通过 E1。
继续优化这个容斥。用 \(X\) 表示碰到直线 \(y=m\),\(Y\) 表示碰到直线 \(y=-1\),需要统计的就是碰撞序列为空或者以 \(X\) 开头的路径。考虑路径终点在 \((n,x)\) 的方案,如果不考虑其他限制,路径数目是 \(C_{n}^{(x-b+n)/2}\)。然后反射容斥,注意 \(x\ge 0\) 和 \(x\lt 0\) 的容斥是不同的, 前者可以写成 \(f(\empty)-f(Y)+f(XY)-f(YXY)+\cdots\),后者则写成 \(f(X)-f(YX)+f(XYX)-\cdots\)。
我们将 \(\ge 0\) 的点和 \(\lt 0\) 的点分别放在一起容斥,会发现每次对称后都是一段连续的 \(C_{n}^{k}\),所以用差分统计每个 \(C_{n}^{k}\) 的系数 \(c_k\) 即可。这里的修改是对某个区间 \([l,r]\),给 \(c_i\) 增加 \((m-1)^{i+d}\) ,因为要考虑往右上走一步的系数是 \(m-1\),而在反射过程中系数是不变的。考虑差分 \(c_i-(m-1)c_{i-1}\) ,然后就做到线性了,可以通过 E2。
代码参考了官方题解。
点击查看代码
Codeforces Global Round 25
Solved:6/9,ABCDEF
2559->2645
A
题意:初始有 \(n\) 盏灯全灭,每次可以选择两盏不相邻的灯点亮,问能否使得灯的点亮情况如给定情形。
题解:如果点亮的灯的数目是奇数则无解,有且仅有两盏灯相邻也无解,否则都有解。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=55;
int T,n,cnt1,cnt2;char s[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);scanf("%s",s+1);cnt1=cnt2=0;
for(int i=1;i<=n;i++)if(s[i]=='1')++cnt1;
for(int i=1;i<n;i++)if(s[i]=='1'&&s[i+1]=='1')++cnt2;
if((cnt1&1)||(cnt1==2&&cnt2==1))printf("NO\n");
else printf("YES\n");
}
return 0;
}
B
题意:有 \(n\) 个人比赛,第一场前两个人比,第 \(i+1\) 场由第 \(i\) 场的胜者与第 \(i+2\) 个人比。假设比赛的胜负由实力的大小关系唯一确定。现在第 \(k\) 个人有一次与别人交换位置的机会,求他最多能赢多少场。
题解:设 \(j\) 是第一个大于 \(a_i\) 的 \(a\) 的下标(如果不存在就为 \(n+1\)),则第 \(i\) 个人能够赢的场数是 \(\max(0,j-i-[i=1])\)。拿一个 set 记录下所有的下标,暴力枚举交换即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,k,a[N],ans;set<int> S;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);S.clear();ans=0;
for(int i=1;i<=n;i++)scanf("%d",a+i);
for(int i=1;i<=n;i++)if(a[i]>a[k])S.insert(i);
if(S.empty()){printf("%d\n",n-1);continue;}
for(int i=1;i<=n;i++){
if(a[i]>a[k])S.insert(k),S.erase(S.find(i));
ans=max(ans,(*S.begin())-i-(i==1));
if(a[i]>a[k])S.insert(i),S.erase(S.find(k));
}
printf("%d\n",ans);
}
return 0;
}
C
题意:一种物品共销售 \(n\) 天,每天最多售卖 \(m\) 件,初始第 \(i\) 天的售价为 \(a_i\)。如果第 \(x\) 天买了 \(y\) 件物品,则第 \(x+1\) 天起每天售价(从原来的价格)增加 \(y\)。问买 \(k\) 件物品的最小代价。
题解:设第 \(i\) 天买 \(x_i\) 件,则最小代价是 \(\sum a_ix_i+\sum_{i\lt j} x_ix_j\)。调整可知至多有一个 \(x_i\) 不是 \(0\) 或 \(m\),那么后半部分的贡献就是定值,前半部分让 \(a\) 升序,\(x\) 降序即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int T,n;ll ans,m,k,a[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld%lld",&n,&m,&k);
for(int i=1;i<=n;i++)scanf("%lld",a+i);
sort(a+1,a+n+1);ans=(k*k-(k/m)*m*m-(k%m)*(k%m))/2;
for(int i=1;i<=k/m;i++)ans+=a[i]*m;ans+=a[k/m+1]*(k%m);
printf("%lld\n",ans);
}
return 0;
}
D
题意:Alice 带着 \(n\) 元钱来 Bob 的货摊上买东西。Bob 可以设立最多 \(60\) 个货摊,每个货摊物品无限,价格可以自己设置。Alice 会贪心地购买,即在每个货摊买尽量多的物品。问 Bob 能否让 Alice 恰好购买 \(k\) 个物品。
题解:如果 \(k=n\) 或 \(k\le \frac{n+1}{2}\) 则有解,构造是 \(n-k+1,1\)。否则无解。其他情况无解的证明是很容易的,考虑第一步操作即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T;long long n,k;
int main(){
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&n,&k);
if(k>(n+1)/2&&k!=n)printf("NO\n");
else printf("YES\n2\n%lld 1\n",n-k+1);
}
return 0;
}
E
题意:请将一个字符串划分为若干段,使得每一段都不是回文串。
题解:如果有解,则存在一种至多分为两段的解,枚举分点用哈希判断。证明分讨即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,P1=998244353,P2=1e9+7;
int T,n;char s[N];
int val[135],pre1[N],suf1[N],pre2[N],suf2[N],p1[N],p2[N];
pair<int,int> hash1(int l,int r){
return make_pair((pre1[r]-1ll*pre1[l-1]*p1[r-l+1]%P1+P1)%P1,
(pre2[r]-1ll*pre2[l-1]*p2[r-l+1]%P2+P2)%P2);
}
pair<int,int> hash2(int l,int r){
return make_pair((suf1[l]-1ll*suf1[r+1]*p1[r-l+1]%P1+P1)%P1,
(suf2[l]-1ll*suf2[r+1]*p2[r-l+1]%P2+P2)%P2);
}
int main(){
srand(time(0));
for(int i=1;i<=130;i++)val[i]=rand();
scanf("%d",&T);
while(T--){
scanf("%s",s+1);n=strlen(s+1);
pre1[0]=suf1[n+1]=pre2[0]=suf2[n+1]=0;p1[0]=p2[0]=1;
for(int i=1;i<=n;i++){
pre1[i]=(131ll*pre1[i-1]+val[s[i]])%P1;
pre2[i]=(137ll*pre2[i-1]+val[s[i]])%P2;
p1[i]=131ll*p1[i-1]%P1;p2[i]=137ll*p2[i-1]%P2;
}
for(int i=n;i>=1;i--){
suf1[i]=(131ll*suf1[i+1]+val[s[i]])%P1;
suf2[i]=(137ll*suf2[i+1]+val[s[i]])%P2;
}
if(hash1(1,n)!=hash2(1,n)){
printf("YES\n1\n%s\n",s+1);
continue;
}
int flag=0;
for(int i=1;i<n;i++)
if(hash1(1,i)!=hash2(1,i)&&hash1(i+1,n)!=hash2(i+1,n)){
printf("YES\n2\n");flag=1;
for(int j=1;j<=i;j++)putchar(s[j]);putchar(' ');
for(int j=i+1;j<=n;j++)putchar(s[j]);putchar('\n');
break;
}
if(!flag)printf("NO\n");
}
return 0;
}
F
题意:给定长为 \(n\) 的排列 \(p\),构造排列 \(q\) 使得 \(q\) 与 \(q\circ p\) 的逆序对数目和等于 \(k\)。
题解:考虑 \(q\) 的逆序对 \((i,j)\)。如果 \((i,j)\) 也是 \(p^{-1}\) 的逆序对,则 \((p^{-1},p^{-1})\) 不是 \(q\circ p\) 的逆序对。类似的讨论可以发现 \(q\circ p\) 的逆序对可以通过 \(q\) 的逆序对异或 \(p^{-1}\) 的逆序对再做一次 \(p^{-1}\) 的变换得来。那么考虑构造 \(q\),只要不断增大 \(q\) 的逆序对集合就可以了,这只要从 \((1\cdots n)\) 开始做 \(n-1\) 轮操作,第 \(i\) 轮依次交换位置 \(i+1\) 与 \(1,2,\cdots,n\)。会发现此时两个排列逆序对数目和不减,所以只要在范围内一定能找到解。先确定轮数,再确定到每一轮的哪一次操作。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int T,n,p[N];ll k,cnt[N];
struct BIT{
ll c[N];
void init(int n){for(int i=1;i<=n;i++)c[i]=0;}
void add(int x,ll v){for(;x<=n;x+=x&-x)c[x]+=v;}
ll ask(int x){ll s=0;for(;x;x-=x&-x)s+=c[x];return s;}
}bit;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&k);
for(int i=1,x;i<=n;i++)
scanf("%d",&x),p[x]=i;
bit.init(n);
for(int i=1;i<=n;i++){
bit.add(p[i],1);
cnt[i]=cnt[i-1]+(i-bit.ask(p[i]));
}
if(k<cnt[n]||k>1ll*n*(n-1)-cnt[n]||(k-cnt[n]&1)){
printf("NO\n");continue;
}printf("YES\n");
if(k==cnt[n]){
for(int i=1;i<=n;i++)printf("%d ",i);
printf("\n");continue;
}
int i;for(i=1;i<=n;i++)
if(1ll*i*(i-1)-2*cnt[i]+cnt[n]>=k)break;--i;
ll now=1ll*i*(i-1)-2*cnt[i]+cnt[n];int j;
for(j=1;j<=i;j++){now+=2*(p[j]<p[i+1]);if(now==k)break;}
for(int x=1;x<=j;x++)printf("%d ",i+2-x);
for(int x=j+1;x<=i;x++)printf("%d ",i+1-x);
printf("%d ",i+1-j);
for(int x=i+2;x<=n;x++)printf("%d ",x);
printf("\n");
}
return 0;
}
G
题意:在一个长为 \(m\) 的环上放 \(n\) 个点,每次等概率选择一个(可能选到已经删除的点),往逆时针方向移动一位,如果目标位置已经有点就删除。问只剩下一个点的期望步数。
题解:考虑用相邻两个点之间的间隔刻画一个状态:\((d_1,d_2,\cdots,d_k)\)。每一步有 \(\frac{1}{n}\) 的概率给 \(d_i\) 加一,\(d_{i+1}\) 减一(模 \(n\) 意义下),然后把变成 \(0\) 的删去。这样可以列出方程。猜测期望式子形如 \(c+\sum f(d_i)\),列出方程,解得 \(f(i)=-\frac{n}{m}\times \frac{i(i-1)(i+1)}{6},c=\frac{n}{m}\times\frac{(m-1)m(m+1)}{6}\)。答案就是 \(\frac{n}{m}(C_{m+1}^{3}-\sum_{i=1}^{k}C_{d_i+1}^{3})\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,P=1e9+7;
int qpow(int a,int b=P-2){
int c=1;
for(;b;b>>=1,a=1ll*a*a%P)
if(b&1)c=1ll*c*a%P;
return c;
}
int T,n,m,a[N],ans;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);ans=0;
for(int i=0;i<n;i++)scanf("%d",a+i);
sort(a,a+n);for(int i=n;i>=0;i--)a[i]-=a[0];
a[n]=m;for(int i=n;i>=1;i--)a[i]-=a[i-1];
ans=1ll*m*(m-1)%P*(m+1)%P;
for(int i=1;i<=n;i++)
ans=(ans-1ll*a[i]*(a[i]-1)%P*(a[i]+1)%P+P)%P;
ans=1ll*ans*n%P*qpow(6ll*m%P)%P;
printf("%d\n",ans);
}
return 0;
}
Pinely Round 3 (Div. 1 + Div. 2)
Solved:7/10,ABCDEF1F2
2494->2559
A
题意:一个机器人在平面上 \((0,0)\) 位置,它只能往上,下,左,右移动,每次一个单位长度,且总共只能使用三种不同的移动方式,问是否能遍历所有给定的点。
题解:如果有点横坐标大于 \(0\),就一定需要向右,否则可以不用。判断一下需要往几个方向走即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,x,y,u,r,d,l;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
u=r=d=l=0;
while(n--){
scanf("%d%d",&x,&y);
if(x>0)r=1;if(x<0)l=1;
if(y>0)u=1;if(y<0)d=1;
}
if(u+d+r+l<4)printf("YES\n");
else printf("NO\n");
}
return 0;
}
B
题意:给定序列 \(a\),找一个数 \(k\) 使得 \(a_i\bmod k\) 对所有 \(i\) 恰好有两个值。\(n\ge 2\) 时一定有解,输出一个解即可。
题解:所有 \(a_i\) 在二进制下最低的不全相同的位权值的两倍是一个可能的解。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n;ll a[105];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",a+i);
for(ll i=2;;i<<=1){
ll r1=a[1]%i,r2=-1;int flag=1;
for(int j=1;j<=n;j++)
if(a[j]%i!=r1&&a[j]%i!=r2){
if(r2==-1)r2=a[j]%i;
else flag=0;
}
if(flag&&r2!=-1){printf("%lld\n",i);break;}
}
}
return 0;
}
C
题意:给定若干个区间 \([l_i,r_i]\),相应的权值为 \(c_i\)。可以重排序列 \(l,r,c\)。假设得到 \(l',r',c'\),在保证 \(l'_i\le r'_i\) 的前提下,最小化 \(\sum c'_i(r'_i-l'_i)\)。保证所有 \(l_i,r_i\) 互不相同。
题解:会发现如果两个区间相交,改成相包含更优,所以答案中不会有区间相交。这样只需要维护一个栈就可以得到左右端点最优的匹配。然后越长的区间分配的 \(c\) 越小。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T,n;ll c[N],ans;
pair<int,int> x[N];
int stk[N],top,len[N],m;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&x[i].first),x[i].second=0;
for(int i=1;i<=n;i++)
scanf("%d",&x[i+n].first),x[i+n].second=1;
for(int i=1;i<=n;i++)scanf("%lld",c+i);
sort(c+1,c+n+1);sort(x+1,x+2*n+1);m=0;
for(int i=1;i<=2*n;i++){
if(x[i].second==0)stk[++top]=x[i].first;
else len[++m]=x[i].first-stk[top],--top;
}
sort(len+1,len+n+1);ans=0;
for(int i=1;i<=n;i++)ans+=c[i]*len[n-i+1];
printf("%lld\n",ans);
}
return 0;
}
D
题意:给定 \(n\) 个正整数和一个常量 \(k\)。每次操作可以选一个数 \(x\),删去它,并写下两个正整数 \(y,z\),要求满足 \(y+z=x+k\)。问最少需要多少次操作使得所有数相同,或报告无解。
题解:把所有数减去 \(k\),会发现操作就是把 \(x\) 拆分成 \(x=y+z\)。那么如果所有数相同答案当然是 \(0\);否则如果有 \(0\) 或者有正有负无解;剩下的情况,最后留下的数一定是所有数的最大公约数,直接计算即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T,n;ll k,a[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lld",a+i),a[i]-=k;
sort(a+1,a+n+1);int flag=1;
if(a[1]==a[n]){printf("0\n");continue;}
if(a[1]<0&&a[n]>0)flag=0;
for(int i=1;i<=n;i++){
if(a[i]==0)flag=0;
if(a[i]<0)a[i]=-a[i];
}
if(!flag){printf("-1\n");continue;}
ll d=a[1],ans=0;for(int i=2;i<=n;i++)d=__gcd(a[i],d);
for(int i=1;i<=n;i++)ans+=a[i]/d;
printf("%lld\n",ans-n);
}
return 0;
}
E
题意:\(n\) 盏灯,初始全灭。\(n\) 个开关,第 \(i\) 个开关能翻转编号为 \(i\) 的倍数的灯的状态。给定若干限制 \((u,v)\),表示如果按下了开关 \(u\) 就要按下开关 \(v\)。构造一种方案,至少按下 \(1\) 个开关,且最后亮着的灯不超过 \([\frac{n}{5}]\) 盏
题解:假设按下所有开关,只有编号是平方数的位置会亮着,那么这就解决了 \(n\ge 20\) 的情况。预处理出 \(n\le 19\),不考虑额外限制时的所有解,这个数目很小,对每组数据枚举所有解判断即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,m,cnt[20],u[N],v[N];
vector<int> res[20];
int main(){
for(int n=1;n<=19;n++){
for(int i=1;i<(1<<n);i++){
for(int j=1;j<=n;j++)if((i>>j-1)&1)
for(int k=j;k<=n;k+=j)cnt[k]^=1;
int num=0;
for(int j=1;j<=n;j++)
num+=cnt[j],cnt[j]=0;
if(num<=n/5)res[n].push_back(i);
}
}
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",u+i,v+i),--u[i],--v[i];
if(n>=20){
printf("%d\n",n);
for(int i=1;i<=n;i++)
printf("%d ",i);
printf("\n");
continue;
}
int flag=0;
for(int x:res[n]){
int ff=1;
for(int i=1;i<=m;i++)
if(((x>>u[i])&1)&&!((x>>v[i])&1))ff=0;
if(ff){
int num=0;
for(int j=1;j<=n;j++)num+=(x>>j-1)&1;
printf("%d\n",num);
for(int j=1;j<=n;j++)if((x>>j-1)&1)
printf("%d ",j);
printf("\n");
flag=1;break;
}
}
if(!flag)printf("-1\n");
}
return 0;
}
F
题意:给定序列 \(a\),\(-1\le a_i\le n\),求 \(1,2,\cdots n\) 的排列 \(p\) 的数目,满足:如果 \(a_i\neq -1\),则 \(p_1,p_2,\cdots p_i\) 中恰有 \(a_i\) 个数不超过 \(i\)。F1有特殊性质:\(a_i\neq -1\)。
题解:设 \(f(i)\) 表示 \(p_{1},\cdots,p_i\) 中不超过 \(i\) 的数目。会发现 \(i\) 对 \(f(j)\) 有贡献当且仅当 \(j\ge \max(i,p_i)\)。也就是说 \(\max(i,p_i)\) 位置的差分增加 \(1\)。
考虑一张二分图,两部都有 \(n\) 个点,左部 \(i\) 点与右部 \(p_i\) 点连边。要求的就是满足两部前 \(i\) 个点的导出子图有 \(a_i\) 条边的方案数。假设某个 \(a_i\neq -1\),上一个 \(a_j\neq -1\),那么首先在 \([1,j]\) 里连了 \(a_j\) 条边,两部都还剩下 \(j-a_j\) 个点;现在要在 \([1,i]\) 内再连 \(a_i-a_j\) 条边,每条边都至少有一个端点在 \([j+1,i]\) 内。对所有 \(i\),找到相应的 \(j\),方案数求积就是答案。
那么就只需要考虑这样一个问题:一张二分图,左右两部各有 \(n+m\) 个点,要连 \(k\) 条边,连的每条边都有至少一个端点在某一部的后 \(m\) 个点,求方案数。设这个问题答案为 \(f(n,m,k)\),再记 \(g(m,k)=f(0,m,k)\)。利用容斥可以得到 \(f(n,m,k)=\sum_{i=0}^{k}(-1)^{i}g(n,i)g(n+m-i,k-i)\)。而 \(g(m,k)=(C_{m}^{k})^2\times k!\),即在两部分别选出 \(k\) 个点进行匹配。所以就可以 \(O(k)\) 计算。这样就在 \(O(n)\) 的时间内解决了问题。注意特判 \(a_n\neq -1,n\) 的情况无解,以及 \(a_i\lt a_j,j\lt i\) 的情况无解。
虽然这题只有2500,但我觉得我nb坏了!
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,P=998244353;
int T,n,a[N],fac[N],inv[N],ans;
void init(int n){
ans=fac[0]=inv[0]=inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=1ll*(P-P/i)*inv[P%i]%P;
for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%P;
for(int i=1;i<=n;i++)inv[i]=1ll*inv[i-1]*inv[i]%P;
}
int g(int m,int k){
if(m<0||k<0||m<k)return 0;
return 1ll*fac[m]*fac[m]%P*inv[m-k]%P*inv[m-k]%P*inv[k]%P;
}
int f(int n,int m,int k){
int res=0;
for(int i=0;i<=k;i++){
if(i&1)res=(res-1ll*g(n,i)*g(n+m-i,k-i)%P+P)%P;
else res=(res+1ll*g(n,i)*g(n+m-i,k-i))%P;
}
return res;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);init(n);
for(int i=1;i<=n;i++)scanf("%d",a+i);
int j=0;if(a[n]==-1)a[n]=n;
if(a[n]!=n){printf("0\n");continue;}
for(int i=1;i<=n;i++)
if(a[i]!=-1){
ans=1ll*ans*f(j-a[j],i-j,a[i]-a[j])%P;j=i;
if(!ans)break;
}
printf("%d\n",ans);
}
return 0;
}
Codeforces Round 905 (Div. 1)
Solved:5/7,A1A2BCD
2413->2494
A2
题意:对两个等长序列 \(a,b\),定义序列对 \((a,b)\) 的权值是将 \(a,b\) 重排后满足 \(a_i\ge b_i\) 的 \(i\) 的数目的最小值。给定 \(a_2,a_3,\cdots,a_n,b_1,b_2,\cdots,b_n\),对 \(a_1=1,2,\cdots,m\),求 \((a,b)\) 的权值的和。
题解:假设 \(x\) 是 \(a_1=1\) 的权值,会发现 \(a_1\) 变化时,权值要么是 \(x\),要么是 \(x+1\),且这样的 \(a_1\) 存在两段性。那么可以先不管 \(a_1\),贪心地给其它 \(a_i\) 匹配 \(b_i\),每次选择能匹配的最小的 \(b_i\)。则当 \(a_1\) 小于剩下来最大的 \(b_i\) 时答案为 \(x\),否则为 \(x+1\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,m,a[N],b[N],vis[N],ans,mx;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);ans=n-1;mx=0;
for(int i=2;i<=n;i++)scanf("%d",a+i);
for(int i=1;i<=n;i++)scanf("%d",b+i),vis[i]=0;
sort(a+2,a+n+1);sort(b+1,b+n+1);
for(int i=2,pos=1;i<=n;i++){
while(pos<=n&&a[i]>=b[pos])++pos;
if(pos<=n)vis[pos]=1,++pos,--ans;
}
for(int i=1;i<=n;i++)
if(!vis[i])mx=max(mx,b[i]-1);
if(m<=mx)printf("%lld\n",1ll*ans*m);
else printf("%lld\n",1ll*ans*m+m-mx);
}
return 0;
}
B
题意:有 \(n\) 个点,\(t\) 个时刻,第 \(i\) 个时刻有 \(m_i\) 条无向边。给定时刻序列 \(a_1,\cdots,a_k\)。现在从点 \(1\) 开始,依次“穿越”到时刻 \(a_1,a_2,\cdots,a_k\),在每个时刻可以移动一条该时刻存在的边或者不移动。求最小的 \(i\),使得可以在 \(a_i\) 到达 \(n\),或者判断到不了 \(n\)。
题解:考虑维护集合 \(S_i\),表示在 \(a_i\) 能到达的点。遇到一个时刻 \(a_i\),如果 \(m_{a_i}\le B\),可以直接遍历时刻 \(a_i\) 的所有边,扩展 \(S_i\);如果 \(m_{a_i}\gt B\),可以考虑与上一次到达时刻 \(a_i\) 的集合的差,遍历这些点的出边。第二种情况中均摊下来每条边最多遍历一次。为了保证这一点,可以用vector
存图,然后记下每个时刻在vector
中的起点和终点。最坏时间复杂度 \(O(nB)\),空间复杂度 \(O(\frac{n^2}{B})\),适当平衡即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool mem1;
typedef pair<int,int> pii;
const int N=2e5+5,M=305;
int n,t,m[N],rev[N],cnt,B,k,lst[N],vis[N],L[N][M],R[N][M];
vector<int> G[N],now;
vector<pii> e[N];
bool mem2;
int main(){
cerr<<(&mem2-&mem1)/1024/1024<<endl;
scanf("%d%d",&n,&t);
B=max(10.0,ceil(1.0*n/M));
for(int i=1;i<=t;i++){
scanf("%d",m+i);
if(m[i]>B){
rev[i]=++cnt;
for(int j=1;j<=n;j++)L[j][cnt]=G[j].size();
}
for(int j=1,u,v;j<=m[i];j++){
scanf("%d%d",&u,&v);
if(m[i]<=B)e[i].push_back({u,v});
else G[u].push_back(v),G[v].push_back(u);
}
if(m[i]>B)for(int j=1;j<=n;j++)R[j][cnt]=G[j].size();
}
now.push_back(1);vis[1]=1;
scanf("%d",&k);
for(int i=1,a;i<=k;i++){
scanf("%d",&a);
int len=now.size();
if(m[a]>B){
for(int x=lst[a];x<len;x++){
int u=now[x],l=L[u][rev[a]],r=R[u][rev[a]];
for(int j=l;j<r;j++){
int v=G[u][j];
if(!vis[v])now.push_back(v),vis[v]=now.size();
}
}lst[a]=len;
}
else{
for(auto x:e[a]){
int u=x.first,v=x.second;
if(vis[u]&&vis[u]<=len&&!vis[v])
now.push_back(v),vis[v]=now.size();
if(!vis[u]&&vis[v]&&vis[v]<=len)
now.push_back(u),vis[u]=now.size();
}
}
if(vis[n]){printf("%d\n",i);return 0;}
}
printf("-1\n");
return 0;
}
C
题意:维护一个序列,支持区间加,求所有时刻的序列中字典序最小的一个。
题解:显然初始的序列是没有用的。考虑从全 \(0\) 的情况开始,则当第一个非零的数是负数时有更小的字典序。然后又可以从这个位置开始当作全 \(0\) 来做。用线段树维护即可,总时间复杂度 \(O(q\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return (f?x:-x);
}
const int N=5e5+5;
int T,n,q,op[N][3],a[N],pos;ll d[N];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
ll mn[N<<2],mx[N<<2],tag[N<<2];
void push_up(int p){mn[p]=min(mn[ls],mn[rs]);mx[p]=max(mx[ls],mx[rs]);}
void make_tag(int p,ll v){mn[p]+=v;mx[p]+=v;tag[p]+=v;}
void push_down(int p){
if(!tag[p])return;ll v=tag[p];tag[p]=0;
make_tag(ls,v);make_tag(rs,v);
}
void modify(int p,int l,int r,int L,int R,ll v){
if(l>=L&&r<=R){make_tag(p,v);return;}
push_down(p);
if(L<=mid)modify(ls,l,mid,L,R,v);
if(R>mid)modify(rs,mid+1,r,L,R,v);
push_up(p);
}
bool query(int p,int l,int r){
if(l==r)return (mn[p]<0);push_down(p);
if(mn[ls]!=0||mx[ls]!=0)return query(ls,l,mid);
else return query(rs,mid+1,r);
}
int main(){
T=read();
while(T--){
n=read();pos=0;
for(int i=1;i<=n;i++)a[i]=read();
q=read();
for(int i=1;i<=q;i++){
int l=read(),r=read(),c=read();
op[i][0]=l;op[i][1]=r;op[i][2]=c;
modify(1,1,n,l,r,c);
if(query(1,1,n)){
for(int j=pos+1;j<=i;j++)
modify(1,1,n,op[j][0],op[j][1],-op[j][2]);
pos=i;
}
}
for(int j=pos+1;j<=q;j++)
modify(1,1,n,op[j][0],op[j][1],-op[j][2]);
for(int i=1;i<=pos;i++)
d[op[i][0]]+=op[i][2],d[op[i][1]+1]-=op[i][2];
for(int i=1;i<=n;i++)d[i]+=d[i-1];
for(int i=1;i<=n;i++)printf("%lld ",a[i]+d[i]);
printf("\n");
for(int i=1;i<=n+1;i++)d[i]=0;
}
return 0;
}
D
题意:一个序列 \(b_1,b_2,\cdots,b_m\) 被称为“可分割的”,当且仅当存在一个 \(1\le x\lt m\),满足对任何 \(1\le i\le x,x\lt j\le m\),有 \(b_i\lt b_j\)。给定一个 \(1\) 到 \(n\) 的排列 \(a\),\(q\) 次询问,判断 \(a_l,\cdots,a_r\) 是否是可分割的。
题解:考虑枚举分界值 \(x\),将小于 \(x\) 的数标为 \(0\),大于等于 \(x\) 的数标为 \(1\)。则 \(x\) 每变化 \(1\),有一个 \(1\) 变为 \(0\)。如果某个时刻 \([l,r]\) 内是前面一段 \(1\),后面一段 \(0\),则区间 \([l,r]\) 是可分割的。每次标数变化的时候,只用考虑变化的那段 \(0\) 使得某些区间可分割。可以用树状数组加二分维护这个事情,每次会得到当右端点在 \([l,r]\) 内,左端点在 \([x,y]\) 内的时候区间可分割。做二维差分,询问变为二维数点,离线树状数组维护即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,a[N],rev[N],m,ans[N];
int c[N];
void add(int x,int v){for(;x<=n;x+=x&-x)c[x]+=v;}
int ask(int x){int res=0;for(;x;x-=x&-x)res+=c[x];return res;}
vector<pair<int,int> > v[N],q[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i),rev[a[i]]=i,add(i,1);
for(int i=1;i<=n;i++){
int x=rev[i];add(x,-1);
int s=ask(x);
int L=1,R=x-1,pos1=0,pos2=n+1,pos3=n+1;
while(L<=R){
int mid=(L+R)>>1;
if(s-ask(mid-1)>0)pos1=mid,L=mid+1;
else R=mid-1;
}++pos1;
L=x+1;R=n;
while(L<=R){
int mid=(L+R)>>1;
if(ask(mid)-s>0)pos3=mid,R=mid-1;
else L=mid+1;
}
if(pos3==n+1)continue;
s=ask(pos3-1);
L=pos3+1;R=n;
while(L<=R){
int mid=(L+R)>>1;
if(ask(mid)-s<=mid-pos3)pos2=mid,R=mid-1;
else L=mid+1;
}--pos2;
x=pos1;int y=pos3-1,l=pos3,r=pos2;
//printf("%d %d %d %d %d\n",i,x,y,l,r);
v[l].push_back({x,1});
if(r!=n)v[r+1].push_back({x,-1});
if(y!=n)v[l].push_back({y+1,-1});
if(y!=n&&r!=n)v[r+1].push_back({y+1,1});
}
scanf("%d",&m);
for(int i=1,l,r;i<=m;i++){
scanf("%d%d",&l,&r);
q[r].push_back({l,i});
}
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){
for(auto x:v[i])add(x.first,x.second);
for(auto x:q[i])ans[x.second]=ask(x.first);
}
for(int i=1;i<=m;i++){
if(ans[i]>0)printf("Yes\n");
else printf("No\n");
}
return 0;
}
Codeforces Round 902 (Div. 1, based on COMPFEST 15 - Final Round)
Solved:3/7,ABC
2410->2413
CodeTON Round 6 (Div. 1 + Div. 2, Rated, Prizes!)
Solved:5/8,ABCDE
2363->2410
Became Grandmaster.
A
题意:给定 \(n,k,x\),构造一个非负整数序列,要求它长度为 \(n\),\(\text{mex}\) 值等于 \(k\),所有元素不超过 \(x\)。在此基础上,最大化整个数列的和。
题解:如果 \(x\lt k-1\) 或 \(n\lt k\),显然无解。剩下的情况先放 \(0,1,\cdots,k-1\),剩下的全部放 \(x\)。但 \(x=k\) 时不能这么做,剩下的只能放 \(k-1\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,k,x;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&k,&x);
if(x<k-1||n<k)printf("-1\n");
else{
int sum=0;
for(int i=1;i<=k;i++)sum+=i-1;
sum+=(n-k)*(x-(x==k));
printf("%d\n",sum);
}
}
return 0;
}
B
题意:给定序列 \(a,b\),可以进行无限次这样的操作:从 \(b\) 中选出一个数 \(x\),\(a\) 中所有数与 \(x\) 取按位或。问操作后 \(a\) 序列的按位异或和的最小值和最大值。
题解:会发现操作对最后值的影响是单调的(只减少或只增加),所以直接计算原序列的按位异或和与进行全部操作的按位异或和即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,m,a[N],b[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",a+i);
int s=0;
for(int i=1;i<=m;i++)
scanf("%d",b+i),s|=b[i];
int v1=0,v2=0;
for(int i=1;i<=n;i++)
v1^=a[i],v2^=(a[i]|s);
if(v1>v2)swap(v1,v2);
printf("%d %d\n",v1,v2);
}
return 0;
}
C
题意:给定序列 \(a\),定义 \(b_{i,j}=\min(a_i,a_j)\)。将 \(b_{i,j}\) 填入一个矩阵的第 \(i\) 行,第 \(j\) 列,对 \(k=1,\cdots,n\),求覆盖所有数字 \(k\) 的矩阵中,面积最小的一个的长宽和。如有多个,取长最小的。
题解;显然对固定的 \(k\),答案是一个正方形。找到最左边和最右边大于等于 \(k\) 的数即可,这可以直接扫描解决。注意如果 \(k\) 不存在需要特判答案为 \(0\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,k,x;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&k,&x);
if(x<k-1||n<k)printf("-1\n");
else{
int sum=0;
for(int i=1;i<=k;i++)sum+=i-1;
sum+=(n-k)*(x-(x==k));
printf("%d\n",sum);
}
}
return 0;
}
D
题意:有一个序列 \(a\),初始全为 \(0\)。给定一个序列 \(c\),\(c_i\) 表示可以花 \(c_i\) 元使得 \(a_j+1\rightarrow a_j,\forall 1\le j \le i\)。一开始有 \(k\) 元钱。在钱足够的前提下,一个操作可以重复做。求最终得到的 \(a\) 中字典序最大的一个。
题解:首先把一些操作单调队列掉:如果 \(c_i\ge c_j,i\le j\),则操作 \(i\) 无意义。在剩下的操作中,先尽可能多做第一个,再尽可能多把第一个换成第二个,以此类推。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,c[N],k,a[N],st[N],cnt[N],top;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",c+i);
scanf("%d",&k);
top=0;
for(int i=1;i<=n;i++){
while(top&&c[st[top]]>=c[i])--top;
st[++top]=i;
}
for(int i=0;i<=n+1;i++)cnt[i]=a[i]=0;
cnt[0]=1e9+7;
for(int i=1;i<=top;i++){
cnt[i]=min(cnt[i-1],k/(c[st[i]]-c[st[i-1]]));
cnt[i-1]-=cnt[i];k-=cnt[i]*(c[st[i]]-c[st[i-1]]);
}
for(int i=1;i<=top;i++)a[st[i]]=cnt[i];
for(int i=n;i>=1;i--)a[i]+=a[i+1];
for(int i=1;i<=n;i++)printf("%d ",a[i]);printf("\n");
}
return 0;
}
E
题意:给定一个序列,请取出它的一些不重叠的连续子序列,使得各个序列的 \(\text{mex}\) 值的异或和最大。
题解:首先可以 \(O(n^2)\) 计算出所有子区间的 \(mex\) 值。考虑一个DP,\(dp_{i,j}\) 表示前 \(i\) 个数能否达到异或和为 \(j\)。则 \(dp_{x,j \text{ xor } mex(u+1,x))}|=dp_{i,j}\)。另外考虑到 \(i\) 可以不取,所以 \(dp_{i,j}|=dp_{i-1,j}\)。
注意到,如果 \(mex(l,r)=mex(l+1,r)\),则不需要考虑 \(l-1\) 到 \(r\) 的转移,因为会在 \(l\) 的时候转移过去。同样的,如果 \(mex(l,r)=mex(l,r-1)\),则不需要考虑 \(l-1\) 到 \(r\) 的转移,会先转移到 \(r-1\) 再转移到 \(r\)。利用这个性质求出所有的转移。感性理解一下这个转移数目应该相当少,应该在 \(O(n)\) 级别,但是我不太会证。不过这么写过了就是了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int T,n,a[N],mex[N][N],cnt[N];
bitset<N*2> dp[N];
vector<int> trans[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",a+i);
for(int i=0;i<=n;i++)dp[i]=0,trans[i].clear();dp[0]=1;
for(int i=1;i<=n;i++){
int res=0;
for(int j=i;j<=n;j++){
++cnt[a[j]];
while(cnt[res])++res;
mex[i][j]=res;
}
for(int j=i;j<=n;j++)--cnt[a[j]];
}
for(int i=1;i<=n;i++){
if(mex[i][i])trans[i-1].push_back(i);
for(int j=i+1;j<=n;j++)
if(mex[i][j]!=mex[i][j-1]&&mex[i][j]!=mex[i+1][j])
trans[i-1].push_back(j);
}
for(int i=0;i<n;i++){
dp[i+1]|=dp[i];
for(int x:trans[i])
for(int j=0;j<8192;j++)
if(dp[i][j])dp[x][j^mex[i+1][x]]=1;
}
for(int j=8191;j>=0;j--)
if(dp[n][j]){printf("%d\n",j);break;}
}
return 0;
}
Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)
2023-8-27
Solved:6/9,ABCDEF
2341->2363
A
题意:构造一个整数列,开头,结尾,长度已经给定,要求严格递增,且差分序列单调递减。若无解,报告之。
题解:从后往前填差分值,前 \(n-2\) 个填 \(1,2,\cdots,n-2\),判断最后一个是否合法。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int T,x,y,n,a[N];
int main(){
ios::sync_with_stdio(false);
cin>>T;
while(T--){
cin>>x>>y>>n;
a[n]=y;a[1]=x;
for(int i=1;i<n-1;i++)
a[n-i]=a[n-i+1]-i;
if(a[2]-a[1]<n-1)cout<<"-1\n";
else{
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<"\n";
}
}
return 0;
}
B
题意:给定一个字符串,可以交换下标差为 \(2\) 的两个位置,也可以反转一段长为 \(k\) 的子串,求能够得到的最小字典序序列。
题解:如果 \(k\) 为奇数,则每个字符所在的下标奇偶性不变,那就只用把奇偶分别排序。如果 \(k\) 为偶数,当 \(k=n\) 时,先按照奇偶分别排序,然后看看哪个放在前面更优;否则,直接将整个序列排序,因为此时容易实现只改变某两个字符下标的奇偶性。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,k;char s[N],a[N],b[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
scanf("%s",s+1);
if(k%2==0){
if(n!=k){
sort(s+1,s+n+1);
for(int i=1;i<=n;i++)printf("%c",s[i]);
}
else{
int m=n/2;
for(int i=1;i<=m;i++)
a[i]=s[2*i-1],b[i]=s[2*i];
sort(a+1,a+m+1);sort(b+1,b+m+1);
int cmp=1;
for(int i=1;i<=m;i++){
if(a[i]<b[i]){cmp=0;break;}
if(a[i]>b[i])break;
}
if(cmp)for(int i=1;i<=m;i++)printf("%c%c",b[i],a[i]);
else for(int i=1;i<=m;i++)printf("%c%c",a[i],b[i]);
}
}
else{
int m1=n-n/2,m2=n/2;
for(int i=1;i<=m1;i++)a[i]=s[2*i-1];
for(int i=1;i<=m2;i++)b[i]=s[2*i];
sort(a+1,a+m1+1);sort(b+1,b+m2+1);
for(int i=1;i<=n/2;i++)printf("%c%c",a[i],b[i]);
if(n&1)printf("%c",a[m1]);
}
printf("\n");
}
return 0;
}
C
题意:给定 \(n\),构造一个序列 \(a\),长度不超过 \(1001\)(记为 \(k\)),满足 \(a_i-a_{i+1}\) 是 \(a_i\) 的因数,\(a_1=n,a_k=1\),且序列 \({a_i-a{i+1}}\) 中任何数字出现次数不超过 \(2\)。
题解:分两步。第一步,不断让 \(n\) 减掉它最大的 \(2^k\) 型因数;第二步,让 \(n\) 不断减掉 \(\frac{n}{2}\)。第一步做完后 \(n\) 一定是 \(2\) 的幂。正确性显然。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
vector<int> ans;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);ans.clear();
for(int i=0;(1<<i)<n;i++)
if(n&(1<<i))ans.push_back(n),n-=(1<<i);
for(;n;n>>=1)ans.push_back(n);
printf("%d\n",(int)ans.size());
for(int x:ans)printf("%d ",x);
printf("\n");
}
return 0;
}
D
题意:给定一个 01 矩阵,最小化操作次数,使得整个矩阵变为全 0。操作形如:选择一个坐标 \((x,y)\),对所有 \((i,j)\) 满足 \(x-i \ge |y-j|\),第 \(i\) 行第 \(j\) 列的数异或 \(1\)。
题解:从上往下,从左往右扫描矩阵,碰到 \(1\) 则对这个位置操作。标记所有操作的位置,实时维护一个前缀和 \(c\),\(c_{i,j}\) 表示所有 \((x,y),x-i \ge |y-j|\) 的位置的操作次数总和,然后判断当前位置是否是 \(1\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3005;
int T,n,ans,a[N][N],b[N][N],c[N][N];char s[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);ans=0;
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=n;j++)a[i][j]=(s[j]=='1');
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int sum=c[i-1][j-1]+c[i-1][j+1]+b[i-1][j];
if(i!=1)sum-=c[i-2][j];
if(sum+a[i][j]&1)b[i][j]=1,++ans;else b[i][j]=0;
c[i][j]=sum+b[i][j];
}
c[i][0]=c[i-1][1];c[i][n+1]=c[i-1][n];
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=b[i][j]=c[i][j]=0;
}
return 0;
}
E
题意:给定一个序列 \(s\)。从中等概率取两个下标(可能相同),设对应的数为 \(a,b\)。把 \(a\) 告诉 Alice,把 \(b\) 告诉 Bob,把 \(a|b\)(按位或)告诉 Alice 和 Bob。两人只知道这些信息,然后开始猜测。他们会轮流说自己的判断,形如:“我不知道 \(a\) 和 \(b\) 的大小关系”或“我知道了”。Alice 先猜,问猜测次数的期望值。
题解:首先固定 \(a,b\),考虑猜测过程。记 \(c=a|b\)。
- 如果 \(a\) 的最高位 \(1\) 不等于 \(c\) 的最高位 \(1\),Alice 直接判断,否则他不知道。
- 如果 \(b\) 的最高位 \(1\) 不等于 \(c\) 的最高位 \(1\),或者 \(b\) 的次高位 \(1\) 不等于 \(c\) 的次高位 \(1\),Bob 直接判断,否则他不知道。
- 如果 \(a\) 的次高位 \(1\) 不等于 \(c\) 的次高位 \(1\),或者 \(a\) 的第三高位 \(1\) 不等于 \(c\) 的第三高位 \(1\),Alice 直接判断,否则他又不知道。
- ……
会发现只要考虑高位的 \(1\),第一次 Alice 看自己 \(1\) 位,接下来每次都是往下看 \(2\) 位,看到 \(0\)(或者看完了)就判断了。
那么只要找出 \(a\) 和 \(b\) 第一个不同的位置,由前面的 \(1\) 的个数以及不同时哪个是 \(1\) 就可以判断这时的猜测次数。建出序列 \(s\) 的 01-Trie,一遍 DFS 就可以求出答案。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,P=998244353;
int qpow(int a,int b=P-2){
int c=1;
for(;b;b>>=1,a=1ll*a*a%P)
if(b&1)c=1ll*c*a%P;
return c;
}
int T,n,trie[N*32][2],tot,len[N*32],cnt[N*32],ans;
void dfs(int u){
if(trie[u][0]&&trie[u][1]){
int lef=cnt[trie[u][0]],rig=cnt[trie[u][1]];
int x=len[u]+1+(len[u]&1),y=len[u]+2-(len[u]&1);
ans=(ans+1ll*lef*rig%P*(x+y)%P)%P;
}
if(trie[u][0])dfs(trie[u][0]);
if(trie[u][1])dfs(trie[u][1]);
if(!trie[u][0]&&!trie[u][1])
ans=(ans+1ll*cnt[u]*cnt[u]%P*(len[u]+1)%P)%P;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);tot=ans=0;
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
int p=0;
for(int j=30;j>=0;j--){
int c=(x>>j)&1;
if(!trie[p][c])
trie[p][c]=++tot,len[tot]=len[p]+(c==1);
p=trie[p][c];++cnt[p];
}
}
dfs(0);ans=1ll*ans*qpow(1ll*n*n%P)%P;
printf("%d\n",ans);
for(int i=0;i<=tot;i++)
len[i]=cnt[i]=trie[i][0]=trie[i][1]=0;
}
return 0;
}
F
题意:给定一个序列,可以选择一些区间,每个区间内的下标对应位置减去一个相同的非负值,要求这些区间两两间要么互相包含,要么互不相交。给出一些询问,每次询问要求:最少需要几次这样的操作,才能使原来值在区间 \([l,r]\) 内的所有数都变成 \(0\)。
题解:首先感性理解一下这个过程,要用这个操作把一段序列减成 \(0\),其实就是每次贪心地选择尽可能长的一段减掉就可以了。也可以理解成:先给整个序列减掉最小值,然后分成若干个子序列,再对每个子序列减掉最小值,以此类推。
对于一个询问,值不在 \([l,r]\) 内的数直接不用考虑了。然后,假设开始加入了所有 \(r\),当加入所有 \(r-1\) 时,它会把原来的 \(r\) 分割成若干段,这时答案增加。再加入 \(r-2\),它分割 \(r-1\) 和 \(r\),又使答案增加。
设 \(f(x,y)\) 表示值在 \([x,y-1]\) 内的数把所有数字 \(y\) 分成了几段,那么询问 \(l,r\) 的答案就是 \(\sum_{y=l}^{r} f(l,y)\)。看到这样的式子,就考虑把询问挂在 \(l\) 上,,扫描 \(l\),用一个数据结构去实时维护 \(f(l,r)\)。
那么我们需要找到 \(f(l+1,r)\) 和 \(f(l,r)\) 的关系。容易发现:当有两个数字 \(r\) 之间的最大的小于 \(r\) 的数是 \(l\) 时,它对 \(f(l,r)-f(l+1,r)\) 贡献 \(1\)。另外 \(r\) 首次出现时对 \(f(r,r)\) 贡献 \(1\)。这样就得到了一个差分数组,有效的差分值只有 \(n\) 个。得到这些差分之后用树状数组支持单点增加,区间查询即可。
最后一个问题是如何找这些差分。方法是使用可持久化线段树,按照数字从小到大加入,维护区间最大值即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,q,a[N],id[N];ll ans[N];
bool cmp(int i,int j){return a[i]<a[j];}
namespace BIT{
ll c[N];
void add(int x,ll v){for(;x<=n;x+=x&-x)c[x]+=v;}
ll ask(int x){ll res=0;for(;x;x-=x&-x)res+=c[x];return res;}
}
namespace seg{
struct node{int mx,lc,rc;}tr[N*50];
int tot;
#define mid ((l+r)>>1)
int build(int l,int r){
int p=++tot;tr[p].mx=0;if(l==r)return p;
tr[p].lc=build(l,mid);tr[p].rc=build(mid+1,r);return p;
}
int modify(int q,int l,int r,int x,int v){
int p=++tot;tr[p]=tr[q];if(l==r){tr[p].mx=v;return p;}
if(x<=mid)tr[p].lc=modify(tr[q].lc,l,mid,x,v);
else tr[p].rc=modify(tr[q].rc,mid+1,r,x,v);
tr[p].mx=max(tr[tr[p].lc].mx,tr[tr[p].rc].mx);return p;
}
int query(int p,int l,int r,int L,int R){
if(l>=L&&r<=R)return tr[p].mx;
int res=0;
if(L<=mid)res=max(res,query(tr[p].lc,l,mid,L,R));
if(R>mid)res=max(res,query(tr[p].rc,mid+1,r,L,R));
return res;
}
}
int root[N];
vector<int> p[N],d[N];
vector<pair<int,int> > Q[N];
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d",a+i),id[i]=i,p[a[i]].push_back(i);
sort(id+1,id+n+1,cmp);root[0]=seg::build(1,n);
for(int i=1;i<=n;i++)
root[a[id[i]]]=seg::modify(root[a[id[i-1]]],1,n,id[i],a[id[i]]);
for(int i=1;i<=n;i++)if(!root[i])root[i]=root[i-1];
for(int i=1;i<=n;i++){
int len=p[i].size();
for(int j=0;j<len-1;j++){
int x=seg::query(root[i-1],1,n,p[i][j],p[i][j+1]);
if(x)d[x].push_back(i);
}
if(len)d[i].push_back(i);
}
for(int i=1,l,r;i<=q;i++){
scanf("%d%d",&l,&r);
Q[l].push_back({r,i});
}
for(int i=n;i>=1;i--){
for(int x:d[i])BIT::add(x,1);
for(auto c:Q[i])
ans[c.second]=BIT::ask(c.first)-BIT::ask(i-1);
}
for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
return 0;
}
Codeforces Round 889 (Div. 1)
2023-7-30
Solved:3/7,A1A2C
2308->2341
A
题意:一个长为 \(n\) 的数列 \(a\),可以将一个数加到另一个数上,要求操作不超过 \(50(\text{A1})/31(\text{A2})\) 次使原序列单调不减。\(n \le 20,-20 \le a_i \le 20\)。
题解:考虑全非负或全非正的情况怎么做。前者从后往前加(即 \(a_i+a_{i+1} \to a_{i+1}\)),后者从后往前加。
一般情况考虑转化。找到绝对值最大的那个数,不妨设为正。如果负数不超过 \(12\) 个,那么给每个负数加上这个最大值,化为全非负的情况;如果负数超过 \(12\) 个,那么找一个负数加自己(翻倍)\(5\) 次后(一定大于 \(20\)),加到所有正数上,化为全非正的情况。容易验证两种情形下步数不超过 \(31\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,a[25],f,ans[55][2],m;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);m=f=0;
for(int i=1;i<=n;i++){
scanf("%d",a+i);
if(a[i]>0)f=i;
}
if(!f){
printf("%d\n",n-1);
for(int i=n;i>1;i--)
printf("%d %d\n",i-1,i);
continue;
}
for(int i=1;i<=6;i++)
ans[++m][0]=f,ans[m][1]=f,a[f]*=2;
ans[++m][0]=1,ans[m][1]=f;a[1]+=a[f];
for(int i=2;i<=n;i++){
while(a[i]<a[i-1]){
ans[++m][0]=i,ans[m][1]=i-1;
a[i]+=a[i-1];
}
}
printf("%d\n",m);
for(int i=1;i<=m;i++)
printf("%d %d\n",ans[i][0],ans[i][1]);
}
return 0;
}
B
题意:一摞 \(n\) 张卡牌,每一张写有一个数,最开始只有第一张为正面。如果当前遇到一张写有 \(v\) 的卡片,可以将接下来 \(v\) 张反面的牌翻面,也可以将自己的得分加 \(v\)。问最大得分。
题解:用 \(f_{i,j}\) 表示是否可以在前 \(i\) 张牌中合法地操作,使得有 \(j\) 张牌被翻面。这里我们假设总共有 \(2n\) 张牌。
固定 \(i\),那么应该让 \(j\) 尽量小(但当然大于等于 \(i\)),这样答案最大,就等于位置 \(i\) 的前缀和减去 \(j\)。那么可以用 bitset 维护 \(f\),用 _Find_first()
函数找尽可能小的 \(j\)。
记得在 \(f\) 全为 \(0\) 时及时 break,不然就会 FST。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,a[N];bitset<N*2> f;long long s[N],ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i),s[i]=s[i-1]+a[i];
f[0]=1;ans=a[1];
for(int i=1;i<=n;i++){
f>>=i-1;f<<=i-1;f|=(f<<a[i]);
if(f.none())break;
int pos=f._Find_first();
ans=max(ans,s[i]-pos);
}
printf("%lld\n",ans);
return 0;
}
C
题意:一个集合 \(S\),是 \(\{1,2,\cdots,m\}\) 的子集。每轮操作等概率选取一个数,删掉它,把它加一的结果丢到 \(S\) 中(如果不超过 \(m\) 且还没有)。问使得 \(S\) 变为空集的期望轮数。
题解:考虑每个数被操作次数的期望。只要考虑它与下一个数的“追尾”。
设 \(f_{x,y}\) 表示 \(m=y,S={1,x}\) 时,\(1\) 被操作的期望次数。则
然后把每个数的期望次数加起来即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=505,P=1e9+7;
int n,m,a[N],f[N][N],ans,inv2=(P+1)/2;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",a+i);
for(int y=2;y<=m;y++){
f[y+1][y]=y;
for(int x=y;x>=2;x--)
f[x][y]=1ll*(f[x-1][y-1]+f[x+1][y]+1)%P*inv2%P;
}
for(int i=1;i<n;i++)
ans=(ans+f[a[i+1]-a[i]+1][m-a[i]+1])%P;
ans=(ans+m-a[n]+1)%P;
printf("%d\n",ans);
return 0;
}
Codeforces Round 872 (Div. 1)
2023-5-8
Solved:3/6,AB1B2
2350->2308
B
题意:给定一棵 \(k\) 个点的树,从中任取 \(k\) 个点,求到这 \(k\) 个点的距离之和最小的点数的期望。
题解:考虑一个点的贡献,那么就是在它的各棵子树中选出一些点,且每棵子树选的点数不超过 \(\frac{k}{2}\)。然后试着推一下组合恒等式,找规律。具体过程不说了,反正代码是对的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,mod=1e9+7;
int n,k,d,ans,sz[N],fac[N],facinv[N],cnt[N];
int head[N],nxt[N<<1],ver[N<<1],tot;
void add(int u,int v){ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
int power(int a,int b){
int c=1;
for(;b;b>>=1){
if(b&1)c=1ll*c*a%mod;
a=1ll*a*a%mod;
}
return c;
}
int C(int n,int m){
if(n<m||n<0||m<0)return 0;
return 1ll*fac[n]*facinv[m]%mod*facinv[n-m]%mod;
}
vector<int> x;
void dfs(int u,int fa){
sz[u]=1;
for(int i=head[u],v;i;i=nxt[i])
if((v=ver[i])!=fa)
dfs(v,u),sz[u]+=sz[v];
if(u!=1)x.push_back(n-sz[u]);
for(int i=head[u],v;i;i=nxt[i])
if((v=ver[i])!=fa)x.push_back(sz[v]);
for(auto p:x)if(p>=d)cnt[p]++;
x.clear();
}
int main(){
scanf("%d%d",&n,&k);
fac[0]=facinv[0]=1;
for(int i=1;i<=n;i++){
fac[i]=1ll*i*fac[i-1]%mod;
facinv[i]=power(fac[i],mod-2);
}
d=k/2+1;
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,-1);
ans=1ll*n*(C(n-1,k)+C(n-1,k-1))%mod;
for(int i=0,r=0;i<=n-d;i++){
r=(r+1ll*C(n-i-d-1,k-d)*C(d+i-1,i)%mod+mod)%mod;
r=(r+1ll*C(n-i-d-1,k-1-d)*C(d+i-1,i)%mod+mod)%mod;
ans=(ans-1ll*r*cnt[i+d]%mod+mod)%mod;
}
ans=1ll*ans*power(C(n,k),mod-2)%mod;
printf("%d\n",ans);
return 0;
}
Codeforces Round 869 (Div. 1)
2023-4-29
Solved:3/6,ABC
2302->2350
第一场Div1.
Good Bye 2022: 2023 is NEAR
2022-12-30
Solved:5/8,ABCDE
2218->2302
Became International Master.
A
题意:给定 \(n\) 个数 \(a_i(1\le i\le n)\),进行 \(m\) 次操作,第 \(j\) 次将某个 \(a_i\) 改为 \(b_j\)。求最终 \(a\) 总和的最大值。
题解:每一次都修改最小值。模拟即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int T,n,m,a[N];
int main(){
scanf("%d",&T);
while(T--){
long long ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",a+i);
sort(a+1,a+n+1);
for(int i=1,b;i<=m;i++){
scanf("%d",&b);
a[1]=b;
for(int j=1;j<n;j++)
if(a[j]>a[j+1])swap(a[j],a[j+1]);
}
for(int i=1;i<=n;i++)ans+=a[i];
printf("%lld\n",ans);
}
return 0;
}
B
题意:给定 \(n,k\),对 \(1,2,...,n\) 的排列 \(P\),定义
构造一个排列使 \(\max c_i\) 取最小值。
题解:总有一个 \(\max\) 会取到 \(n\),所以最小值是 \(2n(k=1)\),\(n+1(k\ge2)\)。构造形如 \(n,1,n-1,2,...\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,k;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)printf("%d ",i&1?n-i/2:i/2);
printf("\n");
}
return 0;
}
C
题意:给定 \(n\) 个数 \(a_i(1\le i\le n)\),问是否存在正整数 \(x\),使得
题解:首先,有两个数相同时无解。
其次,考虑一个模数 \(p\),我们希望最大公约数里不出现 \(p\),也就是不能有两个数 \(\mod p\) 余 \(-x\)。这只需要所有 \(a_i\) 在 \(\mod p\) 意义下有一个余数出现不超过 \(1\) 次。由中国剩余定理可知这也是充分条件。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int T,n,cnt[N],res;
long long a[N];
int main(){
scanf("%d",&T);
while(T--){
res=1;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",a+i);
sort(a+1,a+n+1);
for(int i=1;i<n;i++)if(a[i]==a[i+1])res=0;
for(int i=2,f;i<=n;i++){
f=0;
for(int j=0;j<i;j++)cnt[j]=0;
for(int j=1;j<=n;j++)cnt[a[j]%i]++;
for(int j=0;j<i;j++)if(cnt[j]<=1)f=1;
if(!f)res=0;
}
printf(res?"Yes\n":"No\n");
}
return 0;
}
D
题意:给定长为 \(n\) 的数组 \(a,b\),求长为 \(n\) 的数组 \(c\) 的数目,每个元素在 \([1,n]\) 中取值,且使得 A 在下面的游戏中取胜:
游戏重复以下三个步骤。第 \(i\) 次:
- 令可重集 \(S=\{a_i,b_i,c_i\}\)。
- A在 \(S\) 中删去一个元素。
- B在 \(S\) 的剩余元素中选出一个元素 \(d_i\)。
如果 \(d\) 是 \(1,2,...,n\) 的一个排列,A 获胜,否则 B 获胜。
题解:首先,A 想要获胜,必须每一次都使得 B 有唯一选法。所以每一次 \(S\) 中必须有两个元素相同。
如果在 \(a_i\) 和 \(b_i\) 之间连边,则问题相当于从每条边的两个端点中选出一个,使得每个点恰选出一次。
这要求图的每个连通分支都是基环树,否则无解。如果是,当环是自环时有 \(n\) 种方案,否则有 \(2\) 种方案。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,mod=998244353;
int T,n,a[N],b[N],ans,vis[N],cnt,sum,f;
int head[N],nxt[N<<1],ver[N<<1],tot,deg[N];
void add(int u,int v){
ver[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
deg[u]++;
}
void dfs(int u){
vis[u]=1;cnt++;sum+=deg[u];
for(int i=head[u];i;i=nxt[i]){
if(!vis[ver[i]])dfs(ver[i]);
if(ver[i]==u)f=1;
}
}
int main(){
scanf("%d",&T);
while(T--){
ans=tot=1;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",a+i);
for(int i=1;i<=n;i++)scanf("%d",b+i);
for(int i=1;i<=n;i++){
add(a[i],b[i]);add(b[i],a[i]);
if(a[i]==b[i])ans=1ll*ans*n%mod;
}
for(int i=1;i<=n;i++)
if(!vis[i]){
cnt=sum=0;f=2;dfs(i);
if(sum!=2*cnt)ans=0;
ans=f*ans%mod;
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)vis[i]=0;
for(int i=1;i<=n;i++)head[i]=0;
for(int i=1;i<=n;i++)deg[i]=0;
}
return 0;
}
E
题意:给定一棵树,边有编号,树上某些结点有蝴蝶。现在为每条边依次确定方向,方向的选择是等概率的。确定方向后,如果这条边的起点有蝴蝶而终点没有,就移动这只蝴蝶。求最终所有蝴蝶对距离的平均值的期望。
题解:考虑每条边的贡献,它是这条边两侧的蝴蝶数目之积。
每条边两侧的蝴蝶数目有 \(3\) 种可能,只需要递推算出每个点有蝴蝶的概率,然后就可以求出这条边三种情况(两个方向运送蝴蝶/不运送)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,mod=998244353,inv2=(mod+1)/2;
int n,k,e[N][2],dep[N];
long long a[N],sz[N],ans;
int head[N],ver[N<<1],nxt[N<<1],tot;
int power(int a,int b){
int c=1;
for(;b;b>>=1){
if(b&1)c=1ll*c*a%mod;
a=1ll*a*a%mod;
}
return c;
}
void add(int u,int v){
ver[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void dfs(int u,int fa){
sz[u]=a[u];
for(int i=head[u];i;i=nxt[i])
if(ver[i]!=fa){
dep[ver[i]]=dep[u]+1;
dfs(ver[i],u);
sz[u]+=sz[ver[i]];
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1,x;i<=k;i++){scanf("%d",&x);a[x]=1;}
for(int i=1;i<n;i++){
scanf("%d%d",&e[i][0],&e[i][1]);
add(e[i][0],e[i][1]);add(e[i][1],e[i][0]);
}
dfs(1,-1);
for(int i=1;i<n;i++){
int u=e[i][0],v=e[i][1];
if(dep[u]>dep[v])swap(u,v);
long long p=a[u],q=a[v];
ans=(sz[v]*(k-sz[v])%mod*((mod+1-p)*(mod+1-q)%mod+(p+q)*inv2%mod)%mod+
(sz[v]-1)*(k-sz[v]+1)%mod*(q*(mod+1-p)%mod*inv2%mod)+
(sz[v]+1)*(k-sz[v]-1)%mod*(p*(mod+1-q)%mod*inv2%mod)+ans)%mod;
a[u]=a[v]=(p+q)*inv2%mod;
}
printf("%lld\n",ans*power(1ll*k*(k-1)/2%mod,mod-2)%mod);
return 0;
}
Codeforces Global Round 22
2022-9-30
Solved:6/8,ABCDEF
2102->2218
A
题意:给定两种类型的操作,每种类型有若干个,每个操作有一个收益。两个不同类型得操作相邻可以使得后一个操作收益翻倍,求最大收益。
题解:简单贪心。如果两种操作数目相同就减掉最小的一个,否则取两种最大的一部分,数目均为较少的一种的数目。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,m1,m2,t[N];
long long ans,a[N],b[N];
int main(){
scanf("%d",&T);
while(T--){
ans=m1=m2=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",t+i);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
if(t[i]==0)a[++m1]=x;
else b[++m2]=x;
ans+=x;
}
sort(a+1,a+m1+1);reverse(a+1,a+m1+1);
sort(b+1,b+m2+1);reverse(b+1,b+m2+1);
int m=min(m1,m2);
for(int i=1;i<=min(m1,m2);i++)ans+=a[i]+b[i];
printf("%lld\n",ans-(m1==m2)*min(a[m],b[m]));
}
return 0;
}
B
题意:给定一个单调不减整数列的前缀和的一段后缀,问是否存在这样的数列。
题解:先对已给出的部分判断单调性,然后让前面的数均为最前面那个确定的数,计算初始项即可。注意特判只给出一个数和全部给出的情形。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,k,s[N],a[N],f;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)s[i]=a[i]=0;f=1;
for(int i=n-k+1;i<=n;i++)scanf("%d",s+i);
if(k==1){printf("YES\n");continue;}
for(int i=n-k+1;i<=n;i++)a[i]=s[i]-s[i-1];
if(k!=n&&s[n-k+1]-1ll*(n-k+1)*a[n-k+2]>0)f=0;
for(int i=n-k+1+(k!=n);i<n;i++)if(a[i]>a[i+1])f=0;
printf(f?"YES\n":"NO\n");
}
return 0;
}
C
题意:两个人从一个数列中轮流取数,先手目标是取到的数和为偶数,问先手是否必胜。
题解:博弈论,直接DP。把奇数和偶数的数目及目标作为状态,转移很简单。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int T,n,a[N],c1,c0,dp[N][N][2];
int main(){
scanf("%d",&T);
dp[2][2][0]=1;dp[2][2][1]=0;
dp[3][2][0]=0;dp[2][3][0]=1;
dp[3][2][1]=1;dp[2][3][1]=0;
for(int i=2;i<=N-3;i++)
for(int j=2;j<=N-3;j++)
for(int k=0;k<=1;k++){
if(i+j<=5)continue;
int p1,p2;p1=p2=1;
if(i>=4)p1&=dp[i-2][j][k^1];
if(i>=3&&j>=3)p1&=dp[i-1][j-1][k^1];
if(i<4&&(i<3||j<3))p1=0;
if(i>=3&&j>=3)p2&=dp[i-1][j-1][k];
if(j>=4)p2&=dp[i][j-2][k];
if(j<4&&(i<3||j<3))p2=0;
dp[i][j][k]=p1|p2;
}
while(T--){
c1=c0=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
if(a[i]%2==0)c0++;
else c1++;
}
printf(dp[c1+2][c0+2][0]?"Alice\n":"Bob\n");
}
return 0;
}
D
题意:对于 \(1,2,...,n\) 的排列 \(a_1,a_2,...,a_n\) 及整数 \(k (0 \le k \le n)\),定义数列 \(b_n\):
若 \(a_i \le k\),则 \(b_{a_i}\) 是 \(a_i\) 前面第一个大于 \(k\) 的数(若不存在,则为 \(n+1\))。
若 \(a_i \gt k\),则 \(b_{a_i}\) 是 \(a_i\) 前面第一个小于等于 \(k\) 的数(若不存在,则为 \(0\))。
给定数列 \(b_n\),还原 \(k\) 和 \(a_n\)。
题解:首先确定 \(k\)。处理出数列 \(b_n\) 的前缀最小值和后缀最大值,则 \(k\) 应该是一个满足 \(mn_i \gt i\) 且 \(mx_{i+1} \le i\) 的 \(i\)。
容易发现 \(0\) 和 \(n+1\) 只会出现一个,不妨考虑 \(0\) 出现的情况。从所有出现 \(0\) 的位置找到在数列 \(b_n\) 中还出现的数,这样的数只有一个,它是这一串数的最后一个。这样进行 dfs 即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,k,a[N],b[N],b1[N],tot,mx[N],mn[N];
vector<int> p[N];
void dfs(int x){
int nxt=0;
for(int i=0;i<p[x].size();i++)
if(p[p[x][i]].size())nxt=p[x][i];
else a[++tot]=p[x][i];
if(!nxt)return;
a[++tot]=nxt;
dfs(nxt);
}
void init(){
tot=k=0;
for(int i=0;i<=n+1;i++){
p[i].clear();
a[i]=mx[i]=mn[i]=0;
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
init();
for(int i=1;i<=n;i++){
scanf("%d",b+i);
p[b[i]].push_back(i);
}
mn[0]=1e9;for(int i=1;i<=n;i++)mn[i]=min(mn[i-1],b[i]);
mx[n+1]=0;for(int i=n;i>=1;i--)mx[i]=max(mx[i+1],b[i]);
for(int i=0;i<=n;i++)
if(mn[i]>i&&mx[i+1]<=i)k=i;
if(p[0].size())dfs(0);
else dfs(n+1);
printf("%d\n",k);
for(int i=1;i<=n;i++)printf("%d ",a[i]);
printf("\n");
}
return 0;
}
E
题意:把给定数列分成若干段,使得各段和成回文数列。求方案数。
题解:只用求前缀和数列的子列数,子列中位置对称的数和为总和。用 map 处理出每种数的出现次数。
对于不是总和一半的数 \(x\),设 \(x\) 和 \(sum-x\) 分别有 \(n\),\(m\) 个,则方案数为
这大概可以用组合恒等式直接处理,但是我记不住,所以我推了一步母函数。
上式中的后一个 \(i\) 可以换成 \(n-i\),然后这个式子就相当于 \((1+x)^{m+n}\) 中 \(x^n\) 的系数,就是 \(C_{m+n}^{n}\)。
对于是总和一半的数,设有 \(n\) 个,它取多少都可以,所以是 \(2^n\)。
将以上数目全部乘起来即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,mod=998244353;
int T,n,fac[N],facinv[N],ans;
long long s[N];
map<long long,int> cnt;
int power(int a,int b){
int c=1;
for(;b;b>>=1){
if(b&1)c=1ll*c*a%mod;
a=1ll*a*a%mod;
}
return c;
}
void init(){
cnt.clear();
ans=fac[0]=facinv[0]=1;
for(int i=1;i<=n;i++){
fac[i]=1ll*i*fac[i-1]%mod;
facinv[i]=power(fac[i],mod-2);
}
}
int C(int n,int m){
return 1ll*fac[n]*facinv[m]%mod*facinv[n-m]%mod;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
init();
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
s[i]=s[i-1]+x;
if(i!=n)cnt[s[i]]++;
}
for(int i=1;s[i]*2<s[n];i++)if(s[i]!=s[i+1])
ans=1ll*ans*C(cnt[s[i]]+cnt[s[n]-s[i]],cnt[s[i]])%mod;
if(s[n]%2==0)ans=1ll*ans*power(2,cnt[s[n]/2])%mod;
printf("%d\n",ans);
}
return 0;
}
F
题意:交互题。给出一个无向图的点数 \(n\) 及各点的度,可以对点进行询问,每次得到连接这个点的一条边(不重复)。询问次数不超过 \(n\)。
要求将所有点染色,使得同色点连通,并且某种颜色点的度的和不超过这种颜色点数的平方。
题解:直接猜结论。每次取还未连接的度最大的点,扒出它的所有边。如果它有边连到已经问出来的点就停止,并把这些点加入那个集合。用并查集维护。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int T,n,d[N],fa[N],vis[N],ask;
int find(int x){return (x==fa[x]?x:(fa[x]=find(fa[x])));}
void init(){
ask=0;
for(int i=1;i<=n;i++){fa[i]=i;vis[i]=0;}
}
int main(){
cin>>T;
while(T--){
cin>>n;
init();
for(int i=1;i<=n;i++)cin>>d[i];
while(ask<n){
int u=0;
for(int i=1;i<=n;i++)
if(!vis[i]&&d[i]>d[u])u=i;
if(!u)break;vis[u]=1;
for(int i=1,v;i<=d[u];i++){
cout<<"? "<<u<<endl;
fflush(stdout);
cin>>v;
ask++;
int fu=find(u),fv=find(v);
fa[fu]=fv;
if(vis[v])break;
vis[v]=1;
}
}
cout<<"!";
for(int i=1;i<=n;i++)cout<<" "<<find(i);
cout<<endl;
fflush(stdout);
}
return 0;
}
CodeTON Round 2 (Div. 1 + Div. 2, Rated, Prizes!)
2022-7-31
Solved:4/8,ABCD
2092->2102
Became Master.
Codeforces Round 809 (Div. 2)
2022-7-18
Solved:4/6,ABCD1
2065->2092
Codeforces Round 802 (Div. 2)
2022-6-19
Solved:4/6,ABCD
1916->2065
A
题意:\(1,2,...,n \times m\) 顺次排布在 \(n \times m\) 的表格里,求从左上角走到右下角所经过数和的最小值。
题解:比较明显,先向右再向下即可。证明用调整法。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
printf("%lld\n",1ll*m*(m+1)/2+1ll*m*n*(n+1)/2-m);
}
return 0;
}
B
题意:给定 \(n\) 位数 \(s\),构造另一个 \(n\) 位数,使得这两数的和是回文数。
题解:若 \(s\) 的开头不是 \(9\),让和是 \(99...9\) 即可。否则让和是 \(11...1\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int T,n,a[N];
char s[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
scanf("%s",s+1);
if(s[1]!='9'){for(int i=1;i<=n;i++)printf("%d",'9'-s[i]);}
else{
for(int i=1;i<=n;i++)a[i]=s[n-i+1]-'0';
for(int i=1;i<=n;i++)a[i]=1-a[i];
for(int i=1;i<=n;i++)if(a[i]<0)a[i+1]--,a[i]+=10;
for(int i=n;i>=1;i--)printf("%d",a[i]);
}
printf("\n");
}
return 0;
}
C
题意:给定数列 \(a\),可以进行以下三种操作:一段前缀 \(-1\),一段后缀 \(-1\),整段 \(+1\)。问使得数列全为 \(0\) 的最小步数。
题解:算是比较套路的题。考虑对差分的影响即可,最后补上整体偏移量。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,a[N];
long long ans,a1;
int main(){
scanf("%d",&T);
while(T--){
ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",a+i);
a1=a[1];
for(int i=1;i<n;i++){
int d=a[i+1]-a[i];
if(d<=0)ans-=d,a1+=d;
else ans+=d;
}
printf("%lld\n",ans+abs(a1));
}
return 0;
}
D
题意:若干个桶排成一排,给定各桶容积。要求向一些桶内加 \(t\) 升水,使得所有桶被装满。每个桶超过容积的部分流入右边下一个桶。多个询问。
题解:对于单组询问,有重要性质:每一段前缀是独立的,后面不会影响前面。
那么首先判断第一个行不行,再判断前两个行不行,依此类推。
此时的判断只用比较总水量,因为如果不行是因为前面某个桶没有装满(后面都装满了),则它会在前面被判掉。
所以只用比较前缀平均值与加水量即可。预处理出前缀平均值的最大值即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,a[N],q;
long long s[N],mx;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
s[i]=s[i-1]+a[i];
mx=max(mx,(s[i]-1)/i);
}
scanf("%d",&q);
for(int i=1,t;i<=q;i++){
scanf("%d",&t);
if(t<=mx)printf("-1\n");
else printf("%lld\n",(s[n]-1)/t+1);
}
return 0;
}
E
题意:一个 \(n \times m\) 的表格中填入了 \(1,2,...,n \times m\) 的所有数恰一次。称一种填法可解,当可以找出一条路径,它第一次到达每个格子的顺序与格内所填的数同序。路径的起点,终点,长度任意,可以重复经过同一个格子。
问对给定的填法,至少需要交换几对格子,才能使填法可解。若最小值为 \(0\),输出 \(0\);若最小值为 \(1\),输出 \(1\) 和交换方法数;若最小值大于 \(1\),输出 \(2\)。
题解:看到这个奇怪的问题,可以想到枚举。
首先对原题做一些转化:对于两个相邻的格子,从数字较小的那个向另一个连有向边。
建图之后,“可解”就变成:除了填\(1\)的格子,其它格的入度均不为\(0\)。
那么首先对给定的填法建图(其实只用统计入度),如果已经满足条件,输出\(0\)即可。否则,记 \(cnt\) 为入度为 \(0\) 的格数(不含 \(1\))。
由于只能交换一对格子,所以至多影响10个格子的度,当 \(cnt \gt 10\) 时可以直接输出 \(2\)。
然后,由于入度为 \(0\) 的格需要被消灭,所以其周围 \(4\) 格与本身一定至少一个被改变。枚举这个格子和与之交换的格子即可。注意不要重复枚举。
下面的问题是判定交换两个格子是否满足条件。这并不麻烦。首先把与这两个格子相关的边全部断开,也就是修改入度。注意不要重复。然后交换数字,再连边。在此过程中,记录所涉及点入度的改变量。最后对所有初始入度为 \(0\) 的点判断一下,再对所有涉及的点判断一下,就可以了。注意初始化不能用memset(其实我没试过,不过大概率会T)。
代码写得很丑,开始绕了许多弯路。Link
Codeforces Round 801 (Div. 2) and EPIC Institute of Technology Round
2022-6-18
Solved:3/6,ABC
1874->1916
Became Candidate Master.
Educational Codeforces Round 128 (Rated for Div. 2)
2022-5-13
Solved:5/6,ABCDE
1553->1874
rk22.
A
题意:给定 \(l1,r1,l2,r2\),求满足以下条件的数列长度最小值:
- 最小值出现的次数在 \([l1,r1]\) 内。
- 最大值出现的次数在 \([l2,r2]\) 内。
题解:如果两区间有交集,取交集中最小的数即可。否则答案为 \(l1+l2\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,l1,r1,l2,r2,f;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
f=0;
for(int i=l1;i<=r1;i++)
if(i>=l2&&i<=r2){
f=1;
printf("%d\n",i);
break;
}
if(!f)printf("%d\n",l1+l2);
}
return 0;
}
B
题意:在 \(n \times m\) 的矩阵中给定若干个格子有物品,问是否能将某个物品移到左上角,使得所有物品的相对位置不变,且所有物品都还在矩阵中。
题解:只用判断是否有最左上角的物品。找到最上一排最左边的物品,比较其列与其他物品即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n,m,f,y;
char s[10][10];
int main(){
scanf("%d",&T);
while(T--){
f=1;y=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]=='R'){
if(!y)y=j;
else if(j<y)f=0;
}
printf(f?"YES\n":"NO\n");
}
return 0;
}
C
题意:给定一个01串,可以从开头和结尾删去若干字符,使得剩余的0个数和删去的1个数的最大值最小。
题解:预处理出从开头,结尾删去 \(k\) 个0,最少需要删去的1的个数。然后二分答案,枚举开头删掉0的个数,判断是否可行。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,c0,c1,n0,n1,b[N],e[N],l,r,ans;
char s[N];
bool check(int x){
for(int i=0;i<=n0-x;i++){
int j=n0-x-i;
if(b[i]+e[j]<=x)return true;
}
return false;
}
int main(){
scanf("%d",&T);
while(T--){
n0=n1=0;
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++){
if(s[i]=='0')n0++;
else n1++;
}
c0=c1=b[0]=0;
for(int i=1;i<=n;i++){
if(s[i]=='0'){++c0;b[c0]=c1;}
else ++c1;
}
c0=c1=e[0]=0;
for(int i=n;i>=1;i--){
if(s[i]=='0'){++c0;e[c0]=c1;}
else ++c1;
}
l=0;ans=r=n0;
while(l<=r){
int mid=l+r>>1;
if(check(mid)){ans=mid;r=mid-1;}
else l=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
D
题意:给定一个数列,其中若干个位置为 \(0\),现要将这些 \(0\) 改作 \([-k,k]\) 内的非零整数,最大化前缀和数列的极差。
题解:考虑前缀和数列的最大值和最小值分别在何处取到,枚举这两个位置。预处理0个数前缀和和数列和的前缀和。比较一下可以达到的最大值即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e3+5;
ll n,k,a[N],s[N],cnt[N],ans=-1;
int main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
if(a[i]==0){
cnt[i]=cnt[i-1]+1;
s[i]=s[i-1];
}
else{
cnt[i]=cnt[i-1];
s[i]=s[i-1]+a[i];
}
}
if(abs(s[n])>k*cnt[n]){
printf("-1\n");
return 0;
}
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++){
//1~i increase after i max
//i~j decrease after j min
//j~n increase
long long dis1,dis2,dis3;
dis1=s[i]+k*cnt[i];
dis2=(s[j]-s[i])-k*(cnt[j]-cnt[i]);
dis3=(s[n]-s[j])+k*(cnt[n]-cnt[j]);
ans=max(ans,min(dis1+dis3,-dis2));
}
for(int i=0;i<=n;i++)
for(int j=i;j<=n;j++){
long long dis1,dis2,dis3;
dis1=s[i]-k*cnt[i];
dis2=(s[j]-s[i])+k*(cnt[j]-cnt[i]);
dis3=(s[n]-s[j])-k*(cnt[n]-cnt[j]);
ans=max(ans,min(-dis1-dis3,dis2));
}
printf("%lld\n",ans+1);
return 0;
}
E
题意:在 \(2 \times n\) 的矩阵中给定若干个点,求包含这些点的最小连通块大小。
题解:考虑DP,\(dp_{i,j}\) 中,\(i\) 代表前 \(i\) 列,\(j\) 代表第 \(i\) 列的填法:\(01\),\(10\) 和 \(11\).转移方程是很好写的。
我的代码特判了第一行和第二行分离的情况,其实是没必要的,懒得改了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,INF=1<<30;
int T,n,l1,r1,l2,r2,dp[N][2][2];
char s[2][N];
int main(){
scanf("%d",&T);
while(T--){
l1=l2=r1=r2=0;
scanf("%d",&n);
scanf("%s",s[0]+1);
scanf("%s",s[1]+1);
for(int i=1;i<=n;i++){
if(s[0][i]=='*'){if(!l1)l1=i;r1=i;}
if(s[1][i]=='*'){if(!l2)l2=i;r2=i;}
}
if(l1==0||l2==0){
if(l1==0){swap(l1,l2);swap(r1,r2);}
printf("%d\n",r1-l1);
continue;
}
if(l1>l2){
swap(l1,l2);swap(r1,r2);
for(int i=1;i<=n;i++)swap(s[0][i],s[1][i]);
}
if(r1<=l2)printf("%d\n",r2-l1+1);
else if(r1>=r2){
memset(dp,0x3f,sizeof(dp));
dp[l2-1][0][1]=dp[l2-1][1][0]=dp[l2-1][1][1]=0;
s[0][l2]='*';s[0][r2]='*';
for(int i=l2;i<=r2;i++){
dp[i][1][0]=min(dp[i-1][1][0],dp[i-1][1][1])+1;
dp[i][0][1]=min(dp[i-1][1][1],dp[i-1][0][1])+1;
dp[i][1][1]=min(dp[i-1][1][1],min(dp[i-1][0][1],dp[i-1][1][0]))+2;
if(s[0][i]=='*')dp[i][0][1]=INF;
if(s[1][i]=='*')dp[i][1][0]=INF;
}
printf("%d\n",(l2-l1)+(r1-r2)+min(dp[r2][0][1],dp[r2][1][1])-1);
}
else{
memset(dp,0x3f,sizeof(dp));
dp[l2-1][0][1]=dp[l2-1][1][0]=dp[l2-1][1][1]=0;
s[0][l2]='*';s[1][r1]='*';
for(int i=l2;i<=r1;i++){
dp[i][1][0]=min(dp[i-1][1][0],dp[i-1][1][1])+1;
dp[i][0][1]=min(dp[i-1][1][1],dp[i-1][0][1])+1;
dp[i][1][1]=min(dp[i-1][1][1],min(dp[i-1][0][1],dp[i-1][1][0]))+2;
if(s[0][i]=='*')dp[i][0][1]=INF;
if(s[1][i]=='*')dp[i][1][0]=INF;
}
printf("%d\n",(l2-l1)+(r2-r1)+min(dp[r1][0][1],dp[r1][1][1])-1);
}
}
return 0;
}
Codeforces Round 772 (Div. 2)
2022-2-20
Solved:2/6,AB
1592->1553
不知道在干什么。
Educational Codeforces Round 122 (Rated for Div. 2)
2022-1-31
Solved:3/6,ABD
1476->1592
Codeforces Round 769 (Div. 2)
2022-1-30
Solved:3/6,ABC
1181->1476
Codeforces Global Round 18
2021-12-24
Solved:3/8,ABC
762->1181
Codeforces Round 750 (Div. 2)
2021-10-24
Solved:6/8,ABCDEF1
0->762