BSUIR Open Finals
A. Game with chocolates
因为差值必须是$P$的幂,故首先可以$O(\log n)$枚举出先手第一步所有取法,判断之后的游戏是否先手必败。
对于判断,首先特判非法的情况,并假设$n<m$,则题意可理解成将$n$或者$m$减小至$n-P^k$,在$P$进制下可以理解为$n$某一位减$1$,且这一位在减之前不能是$0$。
若是将$m$减小为$n-P^k$,则整个游戏都是确定的,回合数为$n$的数位之和,根据奇偶性即可判断胜负。
但若是将$n$减小为$m-P^k$,则要求$n$和$m$位数相同且最高位相等,这意味着进行这步操作后之后不能再进行这一步操作,先手可以利用这一步来使自己必胜。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; ll P,n,m,k,a[99],b[99]; bool check(ll n,ll m){ if(n>m)swap(n,m); ll k=m-n; if(m/k%P==0)return 1; while(k%P==0)k/=P; if(k>1)return 1; if(!n)return 0; int la=0,lb=0; while(n)a[++la]=n%P,n/=P; while(m)b[++lb]=m%P,m/=P; if(la==lb&&a[la]==b[lb])return 1; for(int i=2;i<=la;i++)a[1]+=a[i]; return a[1]%2; } int main(){ scanf("%lld%lld%lld",&P,&n,&m); for(k=1;k<=n;k*=P)if(n-k<m)if(!check(n,n-k)){ puts("YES"); printf("%lld %lld",n,n-k); return 0; } for(k=1;k<=m;k*=P)if(m-k<n)if(!check(m-k,m)){ puts("YES"); printf("%lld %lld",m-k,m); return 0; } puts("NO"); }
B. Birches
将相同的数合并,然后调和级数$O(n\log n)$枚举即可。
#include<cstdio> const int N=111111; int n,m,k,i,j,x,l,r,f[N];long long ans; int main(){ scanf("%d%d",&n,&k); while(n--){ scanf("%d",&x); f[x]++; } n=100000; for(i=1;i<=n;i++)if(f[i]&&k<i) for(l=k;l<=n;l+=i)ans+=1LL*f[i]*f[l]; printf("%lld",ans); }
C. Ancient CBS
按平方数分解构造。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; char s[(int)3e5]; int h, t; void solve(int w, int st) { for(int i = st; w >= i; ++i) { s[t++] = '('; s[t++] = ')'; w -= i; } if(w == 0)return; s[--h] = '('; s[t++] = ')'; if(--w == 0)return; solve(w, 2); } int main() { while(~scanf("%d", &n)) //for(int x = 1, n = 1e9 - x; x <= 1000000; ++x, --n) { h = t = 1e5; s[t] = 0; solve(n, 1); s[t] = 0; puts(s + h); /*printf("%d\n", t - h); if(t - h > 1e5) { puts("NO"); while(1); }*/ } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
D. Interactive lock
爆搜得出方案即可。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; int a[100] = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 22, 24, 14, 18, 16, 17, 19, 20, 21, 23, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 78, 44, 45, 46, 47, 48, 49, 50, 51, 100, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 98, 92, 97, 99, 96}; int top = 96; /* bool e[N]; int p[N]; int v[N]; void prime() { int g = 0; int k = 0; for(int i = 2; i <= 10000; ++i) { if(!e[i]) { p[++g] = i; for(int j = i + i; j <= 10000; j += i) { e[j] = 1; } } else { v[++k] = i; } } } */ set<int>can; bool dfs(int g, vector<int>rst) { if(rst.size() == 0) { puts("bingo"); printf("%d\n", g - 1); for(int i = 1; i < g; ++i)printf("%d, ", a[i]); puts(""); return 1; } set<int>::iterator it, nit; for(it = can.begin(); it != can.end(); it = nit) { if(*it > rst.front())return 0; vector<int>nxt; for(auto x : rst)if(x % *it) { nxt.push_back(x - *it); } a[g] = *it; nit = it; ++nit; can.erase(a[g]); if(dfs(g + 1, nxt))return 1; can.insert(a[g]); } /*for(auto it : can) { if(it > rst.front())return 0; vector<int>nxt; for(auto x : rst)if(x % it) { nxt.push_back(x - it); } a[g] = it; can.erase(a[g]); if(dfs(g + 1, nxt))return 1; can.insert(a[g]); }*/ return 0; } void solve() { for(int i = 2; i <= 100; ++i)can.insert(i); vector<int>rst; for(int i = 100; i <= 10000; ++i)rst.push_back(i); dfs(1, rst); } bool guess(int x) { for(int i = 1; i <= top; ++i) { if(a[i] > x) { printf("%d\n", x); return 0; } if(x % a[i] == 0)return 1; x -= a[i]; } return 0; } int main() { //prime(); //solve(); int T; scanf("%d", &T); while(T--) { for(int i = 1; i <= top; ++i) { printf("%d\n", a[i]); fflush(stdout); char s[10]; scanf("%s", s); if(s[0] == 'Y')break; } } /* for(int i = 100; i <= 10000; ++i) { if(!guess(i)) { printf("%d\n", i); } } puts("haha"); */ return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
E. Interval divisibility
对于每个约数计算贡献,分段求和即可。
时间复杂度$O(\sqrt{n})$。
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; const ll P=1000000007,inv2=(P+1)/2; ll f(ll n){ n%=P; return n*(n+1)%P*inv2%P; } ll cal(ll n){ ll ret=0; for(ll i=1;i<=n;){ ll j=n/(n/i); ret+=f(n/i)*((i+j)%P)%P*((j-i+1)%P)%P*inv2%P; ret%=P; i=j+1; } return ret; } int main(){ ll l,r; cin>>l>>r; ll ans=cal(r)-cal(l-1); ans=ans%P; ans=ans+P; ans%=P; cout<<ans; }
F. A trick
分类讨论。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; int main() { while(~scanf("%d", &n)) { if(n == 0) { puts("-1"); continue; } int x = n; int sum = 0; while(x) { sum += x % 10; x /= 10; } if(sum == 9 * 9) { puts("-1"); } else { int tmp1 = sum; int v1 = 0; while(tmp1) { int can = min(tmp1, 9); tmp1 -= can; v1 = v1 * 10 + can; } int tmp2 = sum; int v2 = 0; bool flag = 1; while(tmp2) { int can = min(tmp2, 9 - flag); flag = 0; tmp2 -= can; v2 = v2 * 10 + can; } if(v1 != n)printf("%d\n", v1); else if(v2 != n)printf("%d\n", v2); else printf("%d0\n", v1); } } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
G. Highest ratings year
首先求出所有路径长度之和,并直接除以$2$,那么奇数长度的路径会算错,故再算出奇数长度的路径数即可。
时间复杂度$O(n)$。
#include<cstdio> typedef long long ll; const int N=100010; int n,i,x,y,g[N],v[N<<1],nxt[N<<1],ed; int cnt[2],d[N],size[N]; ll ans; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ d[x]=d[y]^1; cnt[d[x]]++; size[x]=1; for(int i=g[x];i;i=nxt[i])if(v[i]!=y){ dfs(v[i],x); size[x]+=size[v[i]]; ans+=1LL*size[v[i]]*(n-size[v[i]]); } } int main(){ scanf("%d",&n); for(i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); dfs(1,0); ll odd=1LL*cnt[0]*cnt[1]; ans/=2; ans+=odd-odd/2; printf("%lld",ans); }
H. Spells
设$f[i][j]$表示考虑$S$前$i$个位置,被一路交换过来的字符是$j$能匹配成功的字符串个数,当且仅当$j$和当前字符不相同时才进行转移。
那么转移可以写成矩阵的形式,快速幂计算。
对于矩阵的构造,注意到每次单个字符的转移矩阵与$E$相比只有常数个位置不同,故可以利用这点在$O(26)$时间内计算矩阵乘法。
时间复杂度$O(26\sum|S|+n26^3\log k)$。
#include<cstdio> #include<cstring> #define rep(i) for(int i=0;i<N;i++) const int N=28,P=1000000007; int n,m,K,f[N],g[N][N];char s[10010]; inline void mul(){ static int h[N][N]; rep(i)rep(j)h[i][j]=0; rep(i)rep(j)if(g[i][j])rep(k)if(g[j][k])h[i][k]=(1LL*g[i][j]*g[j][k]+h[i][k])%P; rep(i)rep(j)g[i][j]=h[i][j]; } inline void apply(){ static int h[N]; rep(i){ h[i]=0; rep(j)h[i]=(1LL*g[i][j]*f[j]+h[i])%P; } rep(i)f[i]=h[i]; } inline void gao(int x){//2..27 //w[x][0]=1 //w[x][x]=P-1 //w[0][1]=1 //w[0][x]=P-1 //w[1][x]=P-1 //w[1][0]=1 static int h[N][N]; rep(i){ h[0][i]=g[0][i]; h[1][i]=g[1][i]; h[x][i]=g[x][i]; } rep(i){ g[x][i]=(h[0][i]+g[x][i])%P; g[x][i]=(1LL*(P-1)*h[x][i]+g[x][i])%P; g[0][i]=(h[1][i]+g[0][i])%P; g[0][i]=(1LL*(P-1)*h[x][i]+g[0][i])%P; g[1][i]=(1LL*(P-1)*h[x][i]+g[1][i])%P; g[1][i]=(h[0][i]+g[1][i])%P; } } int main(){ scanf("%d",&n); f[0]=1; //1:sum = 2+...+27 while(n--){ scanf("%s%d",s,&K); m=strlen(s); rep(i)rep(j)g[i][j]=i==j; for(int i=0;i<m;i++)gao(s[i]-'a'+2); for(;K;K>>=1,mul())if(K&1)apply(); } printf("%d",f[0]); } /* 7 a 1 n 1 g 1 n 1 g 1 a 1 n 1 6 a 1 n 1 g 1 n 1 g 1 an 1 */
I. Silver table
$n$的方案为将$n-1$的方案复制后放在上下左右四个地方,并将右上块和左下块全体加$2^k-1$,再将左下块反对角线全体加$2^k-1$得到。
#include<cstdio> const int N=3222; int i,j,k,n,f[N][N]; int main(){ f[1][1]=1; for(i=2;i<=11;i++){ int len=1<<(i-1); int hal=len/2; for(j=1;j<=hal;j++)for(k=1;k<=hal;k++){ f[j+hal][k+hal]=f[j][k]; f[j+hal][k]=f[j][k+hal]=f[j][k]+len-1; } for(j=1;j<=hal;j++)f[j+hal][j]+=len-1; } scanf("%d",&n); n=1<<n; for(i=1;i<=n;i++){ for(j=1;j<=n;j++)printf("%d ",f[i][j]); puts(""); } }
J. Soldier’s life
问题等价于找两条间距最小的平行线夹住所有点,故求出凸包后枚举每条边求出最远点即可。
#include<cstdio> #include<algorithm> #include<set> #include<cmath> using namespace std; typedef double DB; const int N=10000; const DB eps = 1e-9, pi = acos(-1.0); DB ans=1e100; int n,m,i; struct PT{ DB x, y; PT(DB x = 0, DB y = 0):x(x), y(y){} void input(){scanf("%lf%lf", &x, &y);} PT operator-(const PT&p)const{return PT(x-p.x,y-p.y);} PT operator+(const PT&p)const{return PT(x+p.x,y+p.y);} PT operator*(double p)const{return PT(x*p,y*p);} PT operator/(double p)const{return PT(x/p,y/p);} bool operator < (const PT &p) const{ if(fabs(x - p.x) > eps) return x < p.x; return y < p.y;} void output(){printf("%.15f %.15f\n", x, y);} DB len()const{return hypot(x,y);} PT rot90()const{return PT(-y,x);} PT trunc(double l)const{return (*this)*l/len();} }a[N],b[N],c[N],fina,finb,f[N],fin[N]; DB lim=1e100; DB cross(const PT&a,const PT&b){return a.x*b.y-a.y*b.x;} DB vect(PT p, PT p1, PT p2){ return (p1.x - p.x) * (p2.y - p.y) - (p1.y - p.y) * (p2.x - p.x); } int convex_hull(PT *p, int n, PT *q){ int i, k, m; sort(p, p + n); m = 0; for(i = 0; i < n; q[m++] = p[i++]) while(m > 1 && vect(q[m - 2], q[m - 1], p[i]) < eps) m --; k = m; for(i = n - 2; i >= 0; q[m ++] = p[i --]) while(m > k && vect(q[m - 2], q[m - 1], p[i]) < eps) m --; return --m; } void solve(PT A,PT B){ DB mx=0; for(int i=0;i<n;i++)mx=max(mx,fabs(cross(a[i]-A,B-A))); mx/=(B-A).len(); mx/=2; if(mx<ans){ ans=mx; fina=A; finb=B; } } PT line_intersection(PT a,PT b,PT p,PT q){ double U=cross(p-a,q-p),D=cross(b-a,q-p); return a+(b-a)*(U/D); } void gao(PT A,PT B){ PT fa=A-B; fa=fa.rot90(); DB ret=0; for(int i=0;i<n;i++){ PT C=a[i],D=C+fa; PT now=line_intersection(A,B,C,D); DB dis=(now-C).len(); ret=max(ret,dis); f[i]=now; } if(ret<lim){ lim=ret; for(int i=0;i<n;i++)fin[i]=f[i]; } } int main(){ scanf("%d",&n); for(i=0;i<n;i++)a[i].input(),b[i]=a[i]; m=convex_hull(b,n,c); for(i=0;i<m;i++)solve(c[i],c[(i+1)%m]); PT tmp=finb-fina; tmp=tmp.rot90(); tmp=tmp.trunc(ans); gao(fina+tmp,finb+tmp); gao(fina-tmp,finb-tmp); printf("%.10f\n",lim); for(i=0;i<n;i++)fin[i].output(); }
K. Casino
DP,$f[n][m]$表示还剩$n$张红卡,$m$张黑卡的最大期望收益。
#include<cstdio> #include<algorithm> using namespace std; const int N=111; int n,m;double f[N][N];bool v[N][N]; double dfs(int n,int m){ if(!n&&!m)return 0; if(v[n][m])return f[n][m]; v[n][m]=1; double ret=0; if(n)ret+=(dfs(n-1,m)+1)*n; if(m)ret+=(dfs(n,m-1)-1)*m; return f[n][m]=max(ret/(n+m),0.0); } int main(){ scanf("%d%d",&n,&m); printf("%.15f",dfs(n,m)); }