XVIII Open Cup named after E.V. Pankratiev. GP of Romania
A. Balance
不难发现确定第一行第一列后即可确定全部,列不等式单纯形求解线性规划即可。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; typedef vector<double>VD; const int N=110; const double eps=1e-9; VD simplex(vector<VD>A, VD b, VD c){ int n = A.size(), m = A[0].size() + 1, r = n, s = m - 1; vector<VD> D(n + 2, VD(m + 1, 0)); vector<int> ix(n + m); for(int i = 0; i < n + m; i ++) ix[i] = i; for(int i = 0; i < n; i ++){ for(int j = 0; j < m - 1; j ++) D[i][j] = -A[i][j]; D[i][m - 1] = 1; D[i][m] = b[i]; if(D[r][m] > D[i][m]) r = i; } for(int j = 0; j < m - 1; j ++) D[n][j] = c[j]; D[n + 1][m - 1] = -1; for(double d; ;){ if(r < n){ int t = ix[s]; ix[s] = ix[r + m]; ix[r + m] = t; D[r][s] = 1.0 / D[r][s]; vector<int> speedUp; for(int j = 0; j <= m; j ++) if(j != s){ D[r][j] *= -D[r][s]; if(D[r][j]) speedUp.push_back(j); } for(int i = 0; i <= n + 1; i ++) if(i != r){ for(int j = 0; j < speedUp.size(); j ++) D[i][speedUp[j]] += D[r][speedUp[j]] * D[i][s]; D[i][s] *= D[r][s]; } } r = -1; s = -1; for(int j = 0; j < m; j ++) if(s < 0 || ix[s] > ix[j]) if(D[n + 1][j] > eps || (D[n + 1][j] > -eps && D[n][j] > eps)) s = j; if(s < 0) break; for(int i = 0; i < n; i ++) if(D[i][s] < -eps) if(r < 0 || (d = D[r][m] / D[r][s] - D[i][m] / D[i][s]) < -eps || (d < eps && ix[r + m] > ix[i + m])) r = i; if(r < 0) return VD(); } if(D[n + 1][m] < -eps) return VD(); VD x(m - 1); for(int i = m; i < n + m; i ++) if(ix[i] < m - 1) x[ix[i]] = D[i - m][m]; printf("%.0f\n",-D[n][m]); return x; } int n,cnt,i,j,k;int a[N][N],f[N][N][N],s[N];double w[N]; int main(){ scanf("%d",&n); for(i=1;i<=n;i++)for(j=1;j<=n;j++)scanf("%d",&a[i][j]); cnt=n*2-1; for(i=1;i<=n;i++){ f[1][i][i]=1; if(i>1)f[i][1][i+n-1]=1; } for(i=2;i<=n;i++)for(j=2;j<=n;j++)for(k=1;k<=cnt;k++)f[i][j][k]=f[i-1][j][k]+f[i][j-1][k]-f[i-1][j-1][k]; for(i=1;i<=n;i++)for(j=1;j<=n;j++)for(k=1;k<=cnt;k++)s[k]+=f[i][j][k]; VD c; vector<VD>A; VD b; for(k=1;k<=cnt;k++)c.push_back(-s[k]); for(i=1;i<=n;i++)for(j=1;j<=n;j++){ b.push_back(-a[i][j]); VD t; for(k=1;k<=cnt;k++)t.push_back(-f[i][j][k]); A.push_back(t); } VD ret=simplex(A,b,c); for(i=1;i<=cnt;i++)w[i]=ret[i-1]; for(i=1;i<=n;puts(""),i++)for(j=1;j<=n;j++){ double t=0; for(k=1;k<=cnt;k++)t+=w[k]*f[i][j][k]; printf("%.0f ",t); } } /* 4 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 */
B. Entanglement
留坑。
C. Gravity
对于每个连通块设$f_x$表示$x$连通块往下掉了多少,对于同一列相邻两个关于$f$建图求最短路即可。
#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() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #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 = 2020, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int n, m; char s[N][N]; const int dy[4] = {-1,0,0,1}; const int dx[4] = {0,-1,1,0}; int id[N][N]; int ID; void go(int y, int x) { id[y][x] = ID; for(int k = 0; k < 4; ++k) { int yy = y + dy[k]; int xx = x + dx[k]; if(s[yy][xx] == '#' && !id[yy][xx]) { go(yy, xx); } } } int boty[N], boto[N]; int f[N * N]; vector< pair<int,int> >a[N * N]; struct A { int x, dis; bool operator < (const A & b)const { return dis > b.dis; } }; void ins(int x, int y, int z) { a[x].push_back({y,z}); } bool e[N * N]; void dijk() { MS(f, 63); f[0] = 0; priority_queue<A>q; q.push({0, 0}); MS(e, 0); while(!q.empty()) { int x = q.top().x; q.pop(); if(e[x])continue; e[x] = 1; for(auto it : a[x]) { int y = it.first; int z = it.second; if(f[x] + z < f[y]) { f[y] = f[x] + z; q.push({y, f[y]}); } } } //puts("dijk finish"); } char ans[N][N]; int main() { while(~scanf("%d%d", &n, &m)) { MS(s, 0); MS(id, 0); ID = 0; 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] == '#' && !id[i][j]) { ++ID; go(i, j); } } } MS(boto, 0); for(int j = 1; j <= m; ++j)boty[j] = n + 1; for(int j = 1; j <= m; ++j) { for(int i = n; i >= 1; --i)if(s[i][j] == '#') { //printf("%d %d\n", i, j); int x = id[i][j]; int y = boto[j]; if(x != y)ins(y, x, boty[j] - i - 1); boto[j] = x; boty[j] = i; } } //continue; //puts("before dijk()"); dijk(); MS(ans, 0); for(int i = 1; i <= n; ++i) { for(int j = 1; j <= m; ++j) { ans[i][j] = '.'; } } for(int i = 1; i <= n; ++i) { for(int j = 1; j <= m; ++j)if(s[i][j] == '#') { int x = id[i][j]; //printf("%d %d %d\n", i, j, f[x]); ans[i + f[x]][j] = '#'; } } for(int i = 1; i <= n; ++i)puts(ans[i] + 1); for(int i = 1; i <= ID; ++i)a[i].clear(); } return 0; } /* 【trick&&吐槽】 10 10 .......... ..######.. ..#....#.. ..#.#..#.. ..#..#.#.. ..#....#.. ..######.. .......... ..#....#.. .......#.. 3 3 #.# .#. ..# 6 6 ###### .....# .###.# .###.# .....# ###### 【题意】 【分析】 【时间复杂度&&优化】 */
D. Infinite Pattern Matching
枚举位数,设$f[i][j][S]$表示还剩低$i$位未考虑,目前KMP指针为$j$,前面KMP转移表为$S$是否搜过,记忆化搜索即可。
#include<cstdio> #include<cstring> #include<string> #include<map> #include<cstdlib> #include<iostream> using namespace std; typedef long long ll; int g[2][60],ans; map<string,int>f[100][60]; int m,i,j,k,nxt[60];char a[60],q[60]; ll pre; int dfs(int n,int x,string s,int prelen,ll pre){ if(!n){ if(s[x]==m){ for(int i=1;i<=prelen&&x<m;i++){ x=g[q[i]][x]; pre++; } cout<<pre; exit(0); } return s[x]; } if(f[n][x].find(s)!=f[n][x].end())return f[n][x][s]; int v=x; for(int i=0;i<2;i++){ string o=s; q[prelen-n+1]=i; for(int j=0;j<=m;j++)o[j]=g[i][s[j]]; v=dfs(n-1,v,o,prelen,pre); pre+=((ll)prelen)<<(n-1); } return f[n][x][s]=v; } int main(){ scanf("%s",a+1); m=strlen(a+1); for(i=1;i<=m;i++)a[i]-='0'; for(nxt[1]=j=0,i=2;i<=m;nxt[i++]=j){ while(j&&a[j+1]!=a[i])j=nxt[j]; if(a[j+1]==a[i])j++; } for(i=0;i<m;i++)for(j=0;j<2;j++){ for(k=i;k&&a[k+1]!=j;k=nxt[k]); if(a[k+1]==j)k++; g[j][i]=k; } g[0][m]=g[1][m]=m; for(i=0;;i++){ string o=""; for(k=0;k<=m;k++)o.push_back(g[1][k]); q[1]=1; ans=dfs(i,ans,o,i+1,pre); pre+=((ll)(i+1))<<i; } }
E. Inheritance
留坑。
F. Movies
留坑。
G. Origami
不难发现行列独立,转化为一维字符串问题。
对于一次折叠,就是边界通过某个偶回文半径进行了翻折,Manacher预处理出回文半径后DP求出每个位置是否可以由左右边界翻折得到即可。
时间复杂度$O(nm)$。
#include<cstdio> typedef long long ll; const int N=2000010; int n,m,i,j,r,p,f[N],g[N],pre[N],suf[N];ll a[N],b[N],s[N];char ch[N]; inline int min(int a,int b){return a<b?a:b;} ll solve(ll*a,int n){ int m; for(i=1;i<=n;i++)s[i<<1]=a[i],s[i<<1|1]=-1; s[0]=-2;s[1]=-1;s[m=(n+1)<<1]=-3; for(r=p=0,f[1]=1,i=2;i<m;i++){ for(f[i]=r>i?min(r-i,f[p*2-i]):1;s[i-f[i]]==s[i+f[i]];f[i]++); if(i+f[i]>r)r=i+f[i],p=i; } for(i=1;i<n;i++)g[i]=f[i<<1|1]>>1; for(pre[0]=i=1;i<n;i++){ j=pre[i-1]; if(i-g[i]-1>=0)j-=pre[i-g[i]-1]; j=!!j; pre[i]=pre[i-1]+j; } ll ret=pre[n-1]; for(suf[n]=1,i=n-1;i;i--){ j=suf[i+1]; if(i+g[i]+1<=n)j-=suf[i+g[i]+1]; j=!!j; if(j)ret+=pre[i-1]; suf[i]=suf[i+1]+j; } return ret; } int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++){ scanf("%s",ch+1); for(j=1;j<=m;j++){ a[i]=a[i]*233+ch[j]; b[j]=b[j]*233+ch[j]; } } printf("%lld",solve(a,n)*solve(b,m)); }
H. Qnp
留坑。
I. Salaj
最优策略下,一定是$1$到$j$作为一个SCC,$j+1$到$n$每个点作为一个SCC,然后$1$到$j$用了$k$个环才得到。
那么任意情况下,不影响SCC情况的多余边数不能超过$\frac{n(n-1)+j(j-1)}{2}$。
设$f[i][j][k]$表示考虑了$i$条边,$1$到$j$作为一个SCC,产生它用了$k$个环的方案数。
转移则是要么将$j$往后扩,要么将当前边作为多余边,可以通过$i,j$和$k$很方便地得到可用边数。
时间复杂度$O(n^3m)$,常数很小,可以通过,但可以通过前缀和优化到$O(n^2m)$。
#include<cstdio> const int N=55,M=55*55; int Case,n,m,P,i,j,k,x,y,f[M][N][N],lim[N],ans[M]; inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;} int main(){ scanf("%d",&Case); while(Case--){ scanf("%d%d%d",&n,&m,&P); if(P==1){ for(i=1;i<=m;i++)printf("0 "); puts(""); continue; } for(i=0;i<=m;i++)for(j=0;j<=n;j++)for(k=0;k<=n;k++)f[i][j][k]=0; for(i=0;i<=n;i++)lim[i]=(n*(n-1)+i*(i-1))/2; f[0][1][0]=1; for(i=0;i<=m;i++)ans[i]=0; for(i=0;i<=m;i++)for(j=1;j<=n;j++){ if(i>lim[j])continue; for(k=0;k<=n;k++){ if(!f[i][j][k])continue; up(ans[i],f[i][j][k]); up(f[i+1][j][k],f[i][j][k]); for(x=j+1;x<=n;x++){ if(i+1-k<x)break; up(f[i+1][x][k+1],f[i][j][k]); } } } for(i=1;i<=m;i++)printf("%d ",ans[i]); puts(""); } }
J. Taxi
若已经确定了所有车和人的位置,则对于树边来说,经过它的对数为两侧车和人数量的较小值。
故枚举每条树边,再枚举一侧的边数,对于另一侧的人数前缀和加速统计贡献即可。
时间复杂度$O(n^2)$。
#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() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #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 = 2520, 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, m; char s[N][N]; vector< pair<int,int> >a[N]; int sz[N]; void getsz(int x, int fa) { sz[x] = 1; for(auto it : a[x])if(it.first != fa) { int y = it.first; getsz(y, x); sz[x] += sz[y]; } } LL pw[N][N]; LL qpow(LL x, LL p) { return pw[x][p]; LL y = 1; while(p) { if(p & 1)y = y * x % Z; x = x * x % Z; p>>=1; } return y; } LL c[2505][2505]; LL C() { c[0][0] = 1; for(int i = 1; i <= 2500; ++i) { c[i][0] = 1; for(int j = 1; j <= 2500; ++j) { c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % Z; } } } LL ans; LL sml[N][N], big[N][N]; void init() { for(int i = 0; i < N; ++i) { pw[i][0] = 1; for(int j = 1; j < N; ++j) { pw[i][j] = pw[i][j - 1] * i % Z; } } //peop for(int i = 0; i <= n; ++i) { for(int j = 0; j <= m; ++j) { sml[i][j] = qpow(i, j) * c[m][j] % Z * qpow(n - i, m - j) % Z * j % Z; if(j)gadd(sml[i][j], sml[i][j - 1]); big[i][j] = qpow(i, j) * c[m][j] % Z * qpow(n - i, m - j) % Z % Z; } for(int j = m - 1; j >= 0; --j) { gadd(big[i][j], big[i][j + 1]); } } } LL cal(int peo_p, int car) { int car_p = n - peo_p; //情况一:peo <= car //peo * qpow(peo_p, peo) * qpow(car_p, m - peo) 前缀和 //情况二:peo > car //car * qpow(peo_p, peo) * qpow(car_p, m - peo) LL rtn = 0; /* for(int i = 0; i <= m; ++i) { gadd(rtn, qpow(peo_p, i) * c[m][i] % Z * qpow(car_p, m - i) % Z * min(i, car) % Z); } */ rtn = (sml[peo_p][car] + car * big[peo_p][car + 1]) % Z; return rtn; } void dfs(int x, int fa) { for(auto it : a[x])if(it.first != fa) { int y = it.first; dfs(y, x); //枚举下面的车的数量 for(int botcar = 0; botcar <= m; ++botcar) { //得到上面的车的数量 int topcar = m - botcar; //考虑了路径的长度,考虑了车的放置方案数 LL tmp = it.second * qpow(sz[y], botcar) % Z * qpow(n - sz[y], topcar) % Z * c[m][topcar] % Z; //gadd(ans, tmp * cal(sz[y], topcar) % Z); gadd(ans, tmp * cal(n - sz[y], botcar) % Z); } } } int main() { C(); while(~scanf("%d%d", &n, &m)) { init(); for(int i = 1; i <= n; ++i)a[i].clear(); for(int i = 1; i < n; ++i) { int x, y, z; scanf("%d%d%d", &x, &y, &z); a[x].push_back({y,z}); a[y].push_back({x,z}); } ans = 0; getsz(1, 0); //puts("before dfs"); dfs(1, 0); printf("%lld\n", ans * 2 % Z); } return 0; } /* 【trick&&吐槽】 5 2 4 5 9805 3 4 2001 2 3 6438 1 3 3790 【题意】 【分析】 【时间复杂度&&优化】 */
K. Tris
留坑。
L. Xormites
首先求出所有数的异或和$sum$,若先手拿到了$A$,则后手必然拿到了$sum\oplus A$。
若$sum=0$,则$A=sum\oplus A$,必定平局。
否则找到$sum$最高位的$1$,那么拿到奇数个$1$的一方获胜,可以将所有数转化为$0$和$1$。
若$n$是偶数,那么将序列黑白染色,必定有一方异或和较大,且先手可以保证自己拿走全部黑或者全部白,故先手必胜。
否则$n$是奇数,若$a_1$和$a_n$都是$0$,那么后手必然可以通过上述方法获胜。
因此先手第一步必须要拿走一个$1$,接下来只能模仿对手行动,检查是否可能即可。
时间复杂度$O(n)$。
#include<cstdio> int Case,n,i,k,sum,a[50010]; bool check(int L,int R){ int l=L,r=R,i,cnt=0; while(l<r&&a[l]==a[r])l++,r--; for(i=l;i<=r;i+=2)if(a[i]^a[i+1])return 0; for(i=L;i<=R;i++)cnt+=a[i]; return cnt/2%2==0; } int solve(){ scanf("%d",&n); sum=0; for(i=1;i<=n;i++){ scanf("%d",&a[i]); sum^=a[i]; } if(!sum)return 0; if(n%2==0)return 1; for(k=30;!(sum>>k&1);k--); for(i=1;i<=n;i++)a[i]=a[i]>>k&1; if(!a[1]&&!a[n])return -1; if(a[1]&&check(2,n))return 1; if(a[n]&&check(1,n-1))return 1; return -1; } int main(){ scanf("%d",&Case); while(Case--){ int t=solve(); if(t==1)puts("First"); if(t==0)puts("Draw"); if(t==-1)puts("Second"); } }