2016 ACM-ICPC 区域赛(大连站)题解整理
A - Wrestling Match (二分图染色)
题意略坑(没有说好的玩家一定能打过差的玩家啊啊~~)
典型的二分图染色问题,每个玩家看成一个点,把相互较量过的玩家之间连边,好的玩家染成黑色,差的玩家染成白色。先把能确定颜色的点都确定下来,然后剩下的点判断是不是二分图,推导过程中发现矛盾立即返回No。如果一个点没有和其他任何点相连且颜色不确定也返回No。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1000+10; 5 int hd[N],ne,n,m,X,Y,col[N]; 6 struct E {int v,nxt;} e[20010]; 7 void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;} 8 bool dfs(int u,int c) { 9 if(~col[u])return col[u]==c; 10 col[u]=c; 11 for(int i=hd[u]; ~i; i=e[i].nxt) { 12 int v=e[i].v; 13 if(!dfs(v,col[u]^1))return 0; 14 } 15 return 1; 16 } 17 bool solve() { 18 for(int u=1; u<=n; ++u)if(~col[u]) { 19 for(int i=hd[u]; ~i; i=e[i].nxt) { 20 int v=e[i].v; 21 if(!dfs(v,col[u]^1))return 0; 22 } 23 } 24 for(int u=1; u<=n; ++u)if(!~col[u]) { 25 if(!~hd[u])return 0; 26 if(!dfs(u,0))return 0; 27 } 28 return 1; 29 } 30 31 int main() { 32 while(scanf("%d%d%d%d",&n,&m,&X,&Y)==4) { 33 memset(hd,-1,sizeof hd),ne=0; 34 memset(col,-1,sizeof col); 35 while(m--) { 36 int u,v; 37 scanf("%d%d",&u,&v); 38 addedge(u,v); 39 addedge(v,u); 40 } 41 while(X--) {int x; scanf("%d",&x); col[x]=1;} 42 while(Y--) {int x; scanf("%d",&x); col[x]=0;} 43 puts(solve()?"YES":"NO"); 44 } 45 return 0; 46 }
B - Regular Number (字符串匹配Shift-And算法+bitset优化)
Shift-And算法,有点抽象,其大致思想是利用前缀移位的方法,对每个数字建立一个bitset,把模板串中所有能出现的位置标上1,然后把原串从头到尾扫一遍,每扫到一个位置,把这个位置加到另一个bitset里,然后和这个位置上的数对应的bitset相与,清除不合法的位置。如果一个位置能够“安全”到达第n-1位,则说明匹配成功,ans++。
时间卡得很死,要用gets和puts才能过。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e6+10; 5 int n; 6 char s[N]; 7 bitset<1000+10> a[10],b; 8 9 int main() { 10 while(scanf("%d",&n)==1) { 11 for(int i=0; i<10; ++i)a[i].reset(); 12 b.reset(); 13 for(int i=0; i<n; ++i) { 14 int x,y; 15 scanf("%d",&x); 16 while(x--) { 17 scanf("%d",&y); 18 a[y].set(i); 19 } 20 } 21 getchar(),gets(s); 22 for(int i=0; s[i]; ++i) { 23 b=(b<<1).set(0)&a[s[i]^48]; 24 if(b.test(n-1)) { 25 char ch=s[i+1]; 26 s[i+1]='\0',puts(s+i-n+1),s[i+1]=ch; 27 } 28 } 29 } 30 return 0; 31 }
C - Game of Taking Stones (威佐夫博弈+高精度浮点数)
威佐夫博弈裸题,(a,b)为必败态当且仅当$a=\frac{\sqrt{5}+1}{2}(b-a)$。但数据较大,可以用Java中的BigDecimal来做,精度开到小数点后100位以上,二分开根号。
1 import java.util.*; 2 import java.io.*; 3 import java.math.*; 4 5 public class Main { 6 static BigDecimal eps=new BigDecimal("0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); 7 static BigDecimal two=BigDecimal.valueOf(2); 8 static BigDecimal sqrt(BigDecimal x) { 9 BigDecimal L=BigDecimal.ZERO,R=x; 10 while(R.subtract(L).compareTo(eps)>0) { 11 BigDecimal mid=(L.add(R).divide(two)); 12 BigDecimal p=mid.pow(2); 13 if(p.compareTo(x)>0)R=mid; 14 else L=mid; 15 } 16 return L; 17 } 18 static BigDecimal sqrt5=sqrt(BigDecimal.valueOf(5)); 19 public static void main(String[] args) throws Exception { 20 Scanner in = new Scanner(System.in); 21 while(in.hasNext()){ 22 BigDecimal a=in.nextBigDecimal(); 23 BigDecimal b=in.nextBigDecimal(); 24 if(a.compareTo(b)>0) { 25 BigDecimal t=b; 26 b=a; 27 a=t; 28 } 29 BigDecimal m=b.subtract(a); 30 BigDecimal k=m.multiply(BigDecimal.ONE.add(sqrt5)).divide(two); 31 BigInteger A=a.toBigInteger(); 32 BigInteger B=k.toBigInteger(); 33 if(A.compareTo(B)==0)System.out.println(0); 34 else System.out.println(1); 35 } 36 } 37 }
D - A Simple Math Problem (数论+一元二次方程)
设X=k1*c,Y=k2*c,k1与k2互质,则c=gcd(X,Y),b=lcm(X,Y)=XY/gcd(X,Y)=k1*k2*c。
又有a=X+Y=(k1+k2)*c,由于k1,k2互质,因此(k1+k2)与k1*k2也互质。
证明:假设(k1+k2)与k1*k2不互质,即含有一个公因数g,则g要么是k1的因子,要么是k2的因子,因此k1,k2中一定有一个能被g整除,那么由于g是(k1+k2)的因子,因此另一个也需要被g整除,与k1,k2互质矛盾。
因此gcd(a,b)=gcd((k1+k2)*c,k1*k2*c)=c=gcd(X,Y),因此原问题转化成了一个一元二次方程X(a-X)=gcd(a,b),用公式求出两个解,判断是否都为正整数就行了。
按理说应该还要判断delta是否大于等于0,但不判也能过~~
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 typedef double db; 5 int a,b; 6 db A,B,C; 7 bool isz(db x) {return x==round(x);} 8 9 int main() { 10 while(scanf("%d%d",&a,&b)==2) { 11 A=1,B=-a,C=(db)b*__gcd(a,b); 12 db delta=B*B-4*A*C; 13 db x=(-B-sqrt(delta))/(2*A); 14 db y=(-B+sqrt(delta))/(2*A); 15 if(isz(x)&&isz(y))printf("%.0f %.0f\n",x,y); 16 else puts("No Solution"); 17 } 18 return 0; 19 }
E - Aninteresting game (二进制规律/树状数组原理)
通过观察二进制位可以得出:
第一种询问的答案是[L,R]中所有数的lowbit之和。[1-x]中以某一位为lowbit的数的个数为n在那一位之前的前缀加上那一位对应的数(0或1),从头到尾刷一遍就行了,有点类似数位dp。
对于第二种询问,一个数x对另一个数y有贡献当且仅当x是y在树状数组中的父节点,用树状数组单点更新的方法即可求出答案。
对long long类型的1移位一定要加ll后缀,一定要加ll后缀,一定要加ll后缀。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 typedef double db; 5 ll n,q; 6 ll lowbit(ll x) {return x&-x;} 7 ll cal(ll x) { 8 ll ret=0; 9 for(ll i=62,j=0; i>=0; j=j<<1|(x>>i&1),--i) { 10 ret+=(j+(x>>i&1))*(1ll<<i); 11 } 12 return ret; 13 } 14 ll cal2(ll x) { 15 ll ret=0; 16 for(; x<=n; x+=lowbit(x))ret++; 17 return ret; 18 } 19 int main() { 20 while(scanf("%lld%lld",&n,&q)==2) { 21 while(q--) { 22 ll f; 23 scanf("%lld",&f); 24 if(f==1) { 25 ll l,r; 26 scanf("%lld%lld",&l,&r); 27 printf("%lld\n",cal(r)-cal(l-1)); 28 } else if(f==2) { 29 ll x; 30 scanf("%lld",&x); 31 printf("%lld\n",cal2(x)); 32 } 33 } 34 } 35 return 0; 36 }
F - Detachment (数学规律)
通过找规律可以发现,如果n=2+3+4+...的话,乘积最大的拆分方案是从2开始的连续一系列的数。可以求出1-N的所有数的前缀和(不包括1)和前缀积,对每个n二分找答案即可。
如果有剩余怎么办?把其中一个数变成右端点的数+1即可。
注意有几个小地方需要特判一下。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e4+10; 5 const int mod=1e9+7; 6 int sum[N],mul[N],inv[N],x,ans; 7 int main() { 8 sum[1]=0,mul[1]=inv[1]=1; 9 for(int i=2; i<N; ++i)sum[i]=sum[i-1]+i,mul[i]=(ll)mul[i-1]*i%mod; 10 for(int i=2; i<N; ++i)inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod; 11 int T; 12 for(scanf("%d",&T); T--;) { 13 scanf("%d",&x); 14 if(x==1)ans=1; 15 else { 16 int p=upper_bound(sum,sum+N,x)-sum-1; 17 int y=x-sum[p],z=p+1-y; 18 ans=mul[p]; 19 if(z==1)ans=(ll)ans*(p+2)%mod*inv[2]%mod; 20 else if(z>1)ans=(ll)ans*(p+1)%mod*inv[z]%mod; 21 } 22 printf("%d\n",ans); 23 } 24 return 0; 25 }
G - Garden of Eden (树形dp+容斥)
答案=全部路径数-不包含1个颜色集合的路径数+不包含2个颜色集合的路径数-不包含3个颜色集合的路径数+...,利用容斥原理,枚举所有不包含颜色的集合,用树形dp搞一搞就行了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e4+10,inf=0x3f3f3f3f; 5 int n,k,a[N],siz[N],hd[N],ne,ppc[1<<10]; 6 ll ans; 7 struct E {int v,nxt;} e[N<<1]; 8 void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;} 9 void dfs(int u,int fa,int f) { 10 for(int i=hd[u]; ~i; i=e[i].nxt)if(e[i].v!=fa)dfs(e[i].v,u,f); 11 if(!siz[u])return; 12 ans+=f; 13 for(int i=hd[u]; ~i; i=e[i].nxt)if(e[i].v!=fa) { 14 int v=e[i].v; 15 ans+=(ll)siz[v]*siz[u]*2*f; 16 siz[u]+=siz[v]; 17 } 18 } 19 ll solve() { 20 ans=0; 21 for(int S=(1<<k)-1; S; --S) { 22 int f=(k-ppc[S])&1?-1:1; 23 for(int i=1; i<=n; ++i)siz[i]=S>>a[i]&1; 24 dfs(1,-1,f); 25 } 26 return ans; 27 } 28 int main() { 29 ppc[0]=0; 30 for(int i=1; i<(1<<10); ++i)ppc[i]=ppc[i>>1]+(i&1); 31 while(scanf("%d%d",&n,&k)==2) { 32 memset(hd,-1,sizeof hd),ne=0; 33 for(int i=1; i<=n; ++i)scanf("%d",&a[i]),a[i]--; 34 for(int i=1; i<n; ++i) { 35 int u,v; 36 scanf("%d%d",&u,&v); 37 addedge(u,v); 38 addedge(v,u); 39 } 40 printf("%lld\n",solve()); 41 } 42 return 0; 43 }
H - To begin or not to begin (概率)
从直觉上可以看出,后手不会比先手有利。进一步当球的数量为奇数的时候,先手会比后手多一次取的机会,而偶数的时候先手和后手取球的机会是均等的,因此球的个数为奇数时输出1,偶数输出0。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int n; 5 6 int main() { 7 while(scanf("%d",&n)==1)printf("%d\n",n&1^1); 8 return 0; 9 }
I - Convex
签到,会求多边形或者三角形面积即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 typedef double db; 5 const db pi=acos(-1); 6 db rad,r; 7 int n; 8 9 int main() { 10 while(scanf("%d%lf",&n,&r)==2) { 11 db ans=0; 12 for(int i=0; i<n; ++i) { 13 scanf("%lf",&rad),rad*=pi/180; 14 ans+=sin(rad); 15 } 16 printf("%.3f\n",ans*r*r/2); 17 } 18 return 0; 19 }
J - Find Small A
签到。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int n; 5 6 int main() { 7 while(scanf("%d",&n)==1) { 8 int x,ans=0; 9 while(n--)for(scanf("%d",&x); x; x>>=8)if((x&((1<<8)-1))==97)ans++; 10 printf("%d\n",ans); 11 } 12 return 0; 13 }
K - Guess the number (dp)
设dp[n]为区间长度为n时的最小最坏猜测次数,则有状态转移方程$dp[n]=min\{max(dp[i],n-i-1)+1\},0<=i<n$,直接算的话是$O(n^2)$会T,可以先暴力打个长度为1000的表找一下规律,然后瞎凑凑出答案来。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e6+10,mod=100000073; 5 int dp[N],siz[N],top[N],bot[N],a,b; 6 7 int main() { 8 memset(top,-1,sizeof top); 9 for(int i=0,j=0; i<N-1; i+=++j)top[i+1]=i,dp[i+1]=j+1; 10 for(int i=0; i<N-1; ++i)if(!~top[i])top[i]=top[i-1]; 11 for(int i=3; i<N; ++i)bot[i]=bot[i-1]+(top[i]==top[i-1]); 12 siz[0]=1,siz[1]=2,siz[2]=4; 13 for(int i=3; i<N; ++i)siz[i]=(siz[i-1]+siz[top[i]]-siz[bot[i]-1])%mod; 14 for(int i=1; i<N; ++i)if(!dp[i])dp[i]=dp[i-1]; 15 while(scanf("%d%d",&a,&b)==2) { 16 int n=b-a+1; 17 printf("%d %d\n",dp[n],((siz[n]-siz[n-1])%mod+mod)%mod); 18 } 19 return 0; 20 }
也可以利用最优区间的单调性将递推复杂度降到$O(n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef unsigned long long ll; 4 const int N=5e6+10,mod=100000073; 5 int dp[N],siz[N],a,b; 6 7 int main() { 8 dp[0]=0,siz[0]=1; 9 for(int i=1,hd=0,tl=1,sum=1; i<N; ++i) { 10 for(; tl<i&&max(dp[tl],i-tl-1)<=max(dp[tl-1],i-(tl-1)-1); sum=(sum+siz[tl++])%mod); 11 for(; hd<tl&&max(dp[hd],i-hd-1)!=max(dp[tl-1],i-(tl-1)-1); sum=(sum-siz[hd++]+mod)%mod); 12 dp[i]=max(dp[hd],i-hd-1)+1,siz[i]=sum; 13 } 14 while(scanf("%d%d",&a,&b)==2)printf("%d %d\n",dp[b-a+1],siz[b-a+1]); 15 return 0; 16 }