2018 ACM-ICPC 区域赛(青岛站)题解整理
C - Flippy Sequence(组合数学+分类讨论)
两区间异或一下,分段考虑,如果全为0则任选两相同区间,答案为$C_{n+1}^{2}=\frac{n(n+1)}{2}$,只有一段连续的1则两区间有一个公共边界,另外两个边界分别为连续1的左右边界,答案为$2C_{n-1}^{1}=2(n-1)$,有两段则两区间平分四个边界,答案为$C_{4}^{2}=6$,三段以上无解。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e6+10; 5 char a[N],b[N]; 6 int n; 7 ll ans; 8 int main() { 9 int T; 10 for(scanf("%d",&T); T--;) { 11 scanf("%d%s%s",&n,a,b); 12 for(int i=0; i<n; ++i)a[i]=a[i]==b[i]?'0':'1'; 13 int t=0; 14 for(int i=0; i<n; ++i)if(a[i]=='1'&&a[i+1]!='1')++t; 15 if(t==0)ans=(ll)n*(n+1)/2; 16 else if(t==1)ans=(ll)(n-1)*2; 17 else if(t==2)ans=6; 18 else ans=0; 19 printf("%lld\n",ans); 20 } 21 return 0; 22 }
D - Magic Multiplication(数学规律+构造)
只要发现一个规律即可:只要A的第一个数确定了,那么剩下的数都可以推出来,而且推的过程中C中不可能出现一位和两位数都可行的情况,因此没有回溯的过程,直接枚举A的第一个数然后判断是否可行就行了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=2e5+10,mod=1e9+7,inf=0x3f3f3f3f; 5 int na,nb,nc,p,a[N],b[N],c[N]; 6 char s[N]; 7 bool Set(int x) { 8 a[0]=x,p=0; 9 for(int i=0; i<nb; ++i) { 10 if(p<nc&&c[p]%a[0]==0&&c[p]/a[0]<=9)b[i]=c[p]/a[0],p+=1; 11 else if(p+1<nc&&(c[p]*10+c[p+1])%a[0]==0&&(c[p]*10+c[p+1])/a[0]<=9)b[i]=(c[p]*10+c[p+1])/a[0],p+=2; 12 else return 0; 13 } 14 return 1; 15 } 16 bool check() { 17 for(int i=1; i<na; ++i) { 18 if(p<nc&&c[p]%b[0]==0&&c[p]/b[0]<=9)a[i]=c[p]/b[0],p+=1; 19 else if(p+1<nc&&(c[p]*10+c[p+1])%b[0]==0&&(c[p]*10+c[p+1])/b[0]<=9)a[i]=(c[p]*10+c[p+1])/b[0],p+=2; 20 else return 0; 21 for(int j=1; j<nb; ++j) { 22 if(p<nc&&a[i]*b[j]==c[p])p+=1; 23 else if(p+1<nc&&a[i]*b[j]==c[p]*10+c[p+1])p+=2; 24 else return 0; 25 } 26 } 27 return p==nc; 28 } 29 int main() { 30 int T; 31 for(scanf("%d",&T); T--;) { 32 scanf("%d%d",&na,&nb); 33 scanf("%s",s),nc=strlen(s); 34 for(int i=0; i<nc; ++i)c[i]=s[i]-'0'; 35 bool f=0; 36 for(int i=1; i<=9; ++i) { 37 if(Set(i)&&check()) { 38 f=1; 39 for(int i=0; i<na; ++i)printf("%d",a[i]); 40 printf(" "); 41 for(int i=0; i<nb; ++i)printf("%d",b[i]); 42 puts(""); 43 break; 44 } 45 } 46 if(!f)puts("Impossible"); 47 } 48 return 0; 49 }
E - Plants vs. Zombies(二分+贪心)
二分答案,然后从左往右走,如果遇到一个点处的值没有满足条件,则在它和它后面的数之间来回走动,判断所需步数是否小于等于m即可。
注意即使当前的数已经满足条件了,也要往后走一步(最后一格除外)
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10; 5 const ll inf=0x3f3f3f3f3f3f3f3f; 6 int n; 7 ll m,a[N],b[N]; 8 bool ok(ll x) { 9 ll cnt=m; 10 for(int i=0; i<n; ++i)a[i]=0; 11 for(int i=0; i<n; ++i) { 12 if(a[i]<x) { 13 ll t=(x-a[i]-1)/b[i]+1; 14 a[i]+=b[i]*t,a[i+1]+=b[i+1]*(t-1); 15 cnt-=2*t-1; 16 } else { 17 if(i==n-1)return 1; 18 cnt--; 19 } 20 if(cnt<0)return 0; 21 } 22 return cnt>=0; 23 } 24 ll bi(ll l,ll r) { 25 while(l<r) { 26 ll mid=(l+r)>>1; 27 ok(mid+1)?l=mid+1:r=mid; 28 } 29 return l; 30 } 31 int main() { 32 int T; 33 for(scanf("%d",&T); T--;) { 34 scanf("%d%lld",&n,&m); 35 for(ll i=0; i<n; ++i)scanf("%lld",&b[i]); 36 printf("%lld\n",bi(0,inf)); 37 } 38 return 0; 39 }
F - Tournament(找规律+构造)
用分治法打出循环赛日程表,然后找规律,发现最多可进行lowbit(n)-1场比赛,依次输出即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1024+10,mod=1e9+7,inf=0x3f3f3f3f; 5 int a[N][N],n,k; 6 int main() { 7 for(int k=1; k<1024; k<<=1) 8 for(int i=0; i<k; ++i) 9 for(int j=0; j<k; ++j) { 10 a[i+k][j]=a[i][j+k]=a[i][j]+k; 11 a[i+k][j+k]=a[i][j]; 12 } 13 int T; 14 for(scanf("%d",&T); T--;) { 15 scanf("%d%d",&n,&k); 16 if(k>=(n&-n))puts("Impossible"); 17 else { 18 for(int i=1; i<=k; ++i) 19 for(int j=0; j<n; ++j)printf("%d%c",a[i][j]+1," \n"[j==n-1]); 20 } 21 } 22 return 0; 23 }
也可以利用异或的性质,将一个排列中的每个数异或上一个相同的数可以实现一定范围内的循环节长度为2的置换,取异或的数分别为1,2,3,...,k分别对应第1,2,3,...,k轮的比赛即可。(不知道为什么这样做答案是正确的)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1024+10,mod=1e9+7,inf=0x3f3f3f3f; 5 int n,k; 6 int main() { 7 int T; 8 for(scanf("%d",&T); T--;) { 9 scanf("%d%d",&n,&k); 10 if(k>=(n&-n))puts("Impossible"); 11 else { 12 for(int i=1; i<=k; ++i) 13 for(int j=0; j<n; ++j)printf("%d%c",(j^i)+1," \n"[j==n-1]); 14 } 15 } 16 return 0; 17 }
G - Repair the Artwork(容斥+dp)
这题卡常比较厉害,我一开始用bfs+循环队列的写法怎么写都是T,后来参考了榜单上大神的代码才AC的。
从左往右递推,设dp[i][j]为递推到第i个数时,可选区间数量为j的方案总数。
先考虑只包含0和1的情况,在这种情况下,每碰到一个1,设这个1的位置为i,它前面的1的位置为i’,则可选区间数量就要加上$C_{i-i'-1}^{2}=\frac{(i-i'-1)(i-i')}{2}$
再考虑包含2的情况,对于每个2,可选区间数为(可选它也可不选它的方案数)-(必须选它的方案数),利用容斥的思想, 可以把每个2拆成一个0和一个1,对答案的贡献即为把它当做0时的贡献减去把它当做1时的贡献。
复杂度$O(n^4)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=100+10,mod=1e9+7; 5 int dp[N][N*(N+1)/2],vis[N][N*(N+1)/2],a[N],n,m,ka; 6 int Pow(int a,int b) { 7 int ret=1; 8 for(; b; b>>=1,a=(ll)a*a%mod)if(b&1)ret=(ll)ret*a%mod; 9 return ret; 10 } 11 void upd(int i,int j,int x) { 12 if(vis[i][j]!=ka)vis[i][j]=ka,dp[i][j]=0; 13 dp[i][j]=(dp[i][j]+x)%mod; 14 } 15 int main() { 16 int T; 17 for(scanf("%d",&T); T--;) { 18 scanf("%d%d",&n,&m),++ka; 19 for(int i=1; i<=n; ++i)scanf("%d",&a[i]); 20 a[n+1]=1,upd(0,0,1); 21 for(int i=0; i<=n; ++i) 22 for(int j=0; j<=i*(i+1)/2; ++j)if(vis[i][j]==ka&&dp[i][j]) 23 for(int k=i+1; k<=n+1; ++k) { 24 if(a[k]==1) {upd(k,j+(k-i)*(k-i-1)/2,dp[i][j]); break;} 25 else if(a[k]==2)upd(k,j+(k-i)*(k-i-1)/2,-dp[i][j]); 26 } 27 ll ans=0; 28 for(int i=0; i<=n*(n+1)/2; ++i)if(vis[n+1][i]==ka)ans=(ans+(ll)dp[n+1][i]*Pow(i,m))%mod; 29 ans=(ans+mod)%mod; 30 printf("%lld\n",ans); 31 } 32 return 0; 33 }
I - Soldier Game(排序+枚举+线段树)
将所有可选划分区间按权值从小到大排序,依次枚举每个区间,将该区间的权值作为最小值,然后再线段树上查询最大值即可。每枚举完一个区间,将这个区间删掉(去掉它在线段树上的贡献)。
对于每个区间,设m为不包含左右边界元素的划分的最大权值,lm为不包含右边界元素的划分的最大权值,mr为不包含左边界元素的划分的最大权值,lmr为包含左右边界元素的划分的最大权值,则可以在$O(1)$的时间内将两区间的信息进行合并。合并时,考虑两区间交界处包含左区间右边界和右区间左边界的区间的贡献即可。
每删除一个区间,线段树上只有包含该区间的区间会受到影响,因此更新的复杂度为$O(logn)$。
总复杂度$O(nlogn)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10; 5 const ll inf=0x3f3f3f3f3f3f3f3f; 6 ll a[N][2]; 7 int n,n2; 8 struct Seg {ll m,lm,mr,lmr;} s[N<<2]; 9 struct P { 10 int x,y; 11 bool operator<(const P& b)const {return a[x][y]<a[b.x][b.y];} 12 } b[N<<1]; 13 #define ls (u<<1) 14 #define rs (u<<1|1) 15 #define mid ((l+r)>>1) 16 void pu(int u,int l,int r) { 17 s[u].m=min(max(a[mid][1],max(s[ls].m,s[rs].m)),max(s[ls].mr,s[rs].lm)); 18 s[u].lm=min(max(a[mid][1],max(s[ls].lm,s[rs].m)),max(s[ls].lmr,s[rs].lm)); 19 s[u].mr=min(max(a[mid][1],max(s[ls].m,s[rs].mr)),max(s[ls].mr,s[rs].lmr)); 20 s[u].lmr=min(max(a[mid][1],max(s[ls].lm,s[rs].mr)),max(s[ls].lmr,s[rs].lmr)); 21 } 22 void build(int u=1,int l=0,int r=n-1) { 23 if(l==r) {s[u].m=inf,s[u].lm=s[u].mr=~inf,s[u].lmr=a[l][0]; return;} 24 build(ls,l,mid),build(rs,mid+1,r),pu(u,l,r); 25 } 26 void upd(int p,int u=1,int l=0,int r=n-1) { 27 if(l==r) {s[u].m=inf,s[u].lm=s[u].mr=~inf,s[u].lmr=a[l][0]; return;} 28 p<=mid?upd(p,ls,l,mid):upd(p,rs,mid+1,r),pu(u,l,r); 29 } 30 int main() { 31 int T; 32 for(scanf("%d",&T); T--;) { 33 scanf("%d",&n); 34 for(int i=0; i<n; ++i)scanf("%lld",&a[i][0]); 35 for(int i=0; i<n-1; ++i)a[i][1]=a[i][0]+a[i+1][0]; 36 a[n-1][1]=inf; 37 build(); 38 n2=0; 39 for(int i=0; i<n; ++i)b[n2++]= {i,0},b[n2++]= {i,1}; 40 sort(b,b+n2); 41 ll ans=inf; 42 for(int i=0; i<n2&&s[1].lmr!=inf; ++i) { 43 ans=min(ans,s[1].lmr-a[b[i].x][b[i].y]); 44 a[b[i].x][b[i].y]=inf,upd(b[i].x); 45 } 46 printf("%lld\n",ans); 47 } 48 return 0; 49 }
J - Books(思维)
先将0元的物品全部买下,如果剩下的m<0或m>n则输出Impossible,m=n则输出Richman,其他情况的最优解释将前m本全部买下,再加上后面的最小值-1。证明比较简单。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10; 5 int n,m,a[N]; 6 int main() { 7 int T; 8 for(scanf("%d",&T); T--;) { 9 scanf("%d%d",&n,&m); 10 for(int i=0; i<n; ++i) { 11 scanf("%d",&a[i]); 12 if(!a[i])i--,n--,m--; 13 } 14 if(m<0||m>n)puts("Impossible"); 15 else if(m==n)puts("Richman"); 16 else printf("%lld\n",accumulate(a,a+m,0ll)+*min_element(a+m,a+n)-1); 17 } 18 return 0; 19 }
K - Airdrop(思维+排序+扫描)
将所有点按x坐标从小到大排序,分别算出作为(x0,y0)左边的点和右边的点时到(x0,y0)的曼哈顿距离(相对的),然后从左往右扫一遍x值,每扫到一个x值,将所有是这个x值的点的曼哈顿距离加进去,如果之前没有在这个距离上存活的点,则这个点存活,否则这个距离上的点全部完蛋,枚举的同时记录到每个x值时有多少存活的点。然后再从右往左扫一遍。最后再扫一遍x值,取左边存活的点+右边存活的点+横坐标为x的点的最大值作为答案。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,inf=0x3f3f3f3f; 5 struct P { 6 int x,y; 7 bool operator<(const P& b)const {return x<b.x;} 8 } p[N]; 9 int n,Y,dis[2][N],f[N<<1],cnt[3][N],mix,mxx; 10 int main() { 11 int T; 12 for(scanf("%d",&T); T--;) { 13 memset(cnt,0,sizeof cnt); 14 scanf("%d%d",&n,&Y); 15 for(int i=1; i<=n; ++i)scanf("%d%d",&p[i].x,&p[i].y),p[i].x++; 16 sort(p+1,p+1+n),mix=p[1].x,mxx=p[n].x; 17 for(int i=1; i<=n; ++i) { 18 dis[0][i]=abs(Y-p[i].y)-p[i].x+100000; 19 dis[1][i]=abs(Y-p[i].y)+p[i].x; 20 } 21 memset(f,0,sizeof f); 22 for(int i=mix,j=1,k; i<=mxx; ++i) { 23 cnt[0][i]=cnt[0][i-1]; 24 k=j; 25 for(; j<=n&&p[j].x==i; ++j) { 26 if(f[dis[0][j]]==0)f[dis[0][j]]=1,cnt[0][i]++; 27 else if(f[dis[0][j]]==1)f[dis[0][j]]=-1,cnt[0][i]--; 28 } 29 j=k; 30 for(; j<=n&&p[j].x==i; ++j)if(f[dis[0][j]]==-1)f[dis[0][j]]=0; 31 } 32 memset(f,0,sizeof f); 33 for(int i=mxx,j=n,k; i>=mix; --i) { 34 cnt[1][i]=cnt[1][i+1]; 35 k=j; 36 for(; j>=1&&p[j].x==i; --j) { 37 if(f[dis[1][j]]==0)f[dis[1][j]]=1,cnt[1][i]++; 38 else if(f[dis[1][j]]==1)f[dis[1][j]]=-1,cnt[1][i]--; 39 } 40 j=k; 41 for(; j>=1&&p[j].x==i; --j)if(f[dis[1][j]]==-1)f[dis[1][j]]=0; 42 } 43 for(int i=1; i<=n; ++i)cnt[2][p[i].x]++; 44 int ans1=~inf,ans2=inf; 45 for(int i=mix-1; i<=mxx+1; ++i) { 46 ans1=max(ans1,cnt[0][i-1]+cnt[1][i+1]+cnt[2][i]); 47 ans2=min(ans2,cnt[0][i-1]+cnt[1][i+1]+cnt[2][i]); 48 } 49 printf("%d %d\n",ans2,ans1); 50 } 51 return 0; 52 }
L - Sub-cycle Graph(组合数学+生成函数)
一个连接n个点的环包含m条边的子图中,一定包含k=n-m个连通分量,其中每个连通分量要么是一个点,要么是包含多个点的一条链,则问题转化成把n个顶点划分成k个连通分量的方案数。我们可以对所有点进行染色,将同一连通分量中的点染成相同的颜色,则问题转化成将n个点染成k种染色的方案数(注意是带权的方案数,因为将一些点染成相同的颜色(放进同一个连通分量)的方法有多种)。
用k种颜色给n个点染色的生成函数(指数型)为
$f(x)=\sum_{i=1}^{k}\frac{a_i}{i!}x^i,a_i=\left\{\begin{matrix}\begin{aligned}&1,i=1\\&\frac{i!}{2},i>1\end{aligned}\end{matrix}\right.$
其中ai的含义是将i个点染成同一颜色(放进同一连通分量)的方案数。
上式化简得:
$f(x)=\frac{1}{2}(2x+x^2+x^3+...+x^k)=\frac{1}{2}x(\frac{2-x}{1-x})$
则答案的生成函数为:
$[f(x)]^k=\frac{1}{2^k}x^k(2-x)^k\frac{1}{(1-x)^k}$
所求的结果即为上式中x^n项的系数乘上n!再除以k!(因为各种颜色是没有差别的,需要去序)。
其中$(2-x)^k$中项的系数可以用二项式定理求,而后面的$\frac{1}{(1-x)^k}$比较特殊,与这个函数相乘,相当于对整个多项式求了k次前缀和,自己算一算就会发现,求k次前缀和后第n项的系数为
$\sum_{i=0}^{n}C_{k-1+n-i}^{k-1}a_i$
因此每次可以在O(n)的时间内算出答案。
注意k=0和k<0的情况要特殊处理。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const ll N=1e5+10,inf=0x3f3f3f3f,mod=1e9+7; 5 ll a[N],n,k,f[N],invf[N],inv[N],ans,invp2[N]; 6 ll C(ll a,ll b) {return f[a]*invf[b]%mod*invf[a-b]%mod;} 7 int main() { 8 f[0]=invf[0]=f[1]=invf[1]=1,inv[1]=1; 9 for(ll i=2; i<N; ++i) { 10 inv[i]=(mod-mod/i)*inv[mod%i]%mod; 11 f[i]=f[i-1]*i%mod,invf[i]=invf[i-1]*inv[i]%mod; 12 } 13 invp2[0]=1; 14 for(ll i=1; i<N; ++i)invp2[i]=invp2[i-1]*inv[2]%mod; 15 ll T; 16 for(scanf("%lld",&T); T--;) { 17 scanf("%lld%lld",&n,&k),k=n-k; 18 if(k<0)ans=0; 19 else if(k==0)ans=f[n-1]*inv[2]%mod; 20 else { 21 ans=0; 22 for(ll i=0; i<=n; ++i)a[i]=0; 23 for(ll i=0; i<=k&&i+k<=n; ++i) { 24 a[i+k]=C(k,i)*invp2[i]%mod; 25 if(i&1)a[i+k]=-a[i+k]; 26 } 27 for(ll i=0; i<=n; ++i)ans=(ans+C(k-1+n-i,k-1)*a[i])%mod; 28 ans=(ans*f[n]%mod*invf[k]%mod+mod)%mod; 29 } 30 printf("%lld\n",ans); 31 } 32 return 0; 33 }
M - Function and Function(循环节)
算到0的时候直接根据k值的奇偶出结果就行,注意n=0的时候需要特判。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int a[]= {1,0,0,0,1,0,1,0,2,1}; 5 int n,k; 6 int ff(int n) { 7 if(!n)return 1; 8 int ret=0; 9 for(; n; n/=10)ret+=a[n%10]; 10 return ret; 11 } 12 int f(int n,int k) { 13 while(k--) { 14 n=ff(n); 15 if(!n)return k&1; 16 } 17 return n; 18 } 19 int main() { 20 int T; 21 for(scanf("%d",&T); T--;) { 22 scanf("%d%d",&n,&k); 23 printf("%d\n",f(n,k)); 24 } 25 return 0; 26 }