CODE FESTIVAL 2016 Final
链接
D. Pair Cards
对于 \(i\in[0,m)\) 统计有多少对相同的数 \(x\mod m=i\),以及有多少数 \(x\mod m=i\)。考虑贪心匹配,首先 \(0\) 和 \(\frac m2\) 直接自己匹配,对于 \(i\in[1,\frac m2)\),将它与 \(m-i\) 匹配,优先将散的匹配对面的,然后再用一对的匹配,如果匹配光了就自己匹配。
正确性显然。复杂度 \(O(n+m)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
using namespace std;
int a[N],b[N],c[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int mx=0;
for(int i=1,x;i<=n;i++) scanf("%d",&x),c[x]++,mx=max(mx,x);
for(int i=1;i<=mx;i++) a[i%m]+=c[i]&1,b[i%m]+=c[i]/2;
int res=a[0]/2+b[0];
if(m%2==0) res+=a[m/2]/2+b[m/2];
for(int i=1;i*2<m;i++)
{
int v=min(a[i],a[m-i]);
res+=v;a[i]-=v,a[m-i]-=v;
if(a[i]){v=min(a[i]/2,b[m-i]);res+=v*2;a[i]-=v*2,b[m-i]-=v;}
if(a[m-i]){v=min(a[m-i]/2,b[i]);res+=v*2;a[m-i]-=v*2,b[i]-=v;}
res+=b[i]+b[m-i];
}
printf("%d\n",res);
return 0;
}
E. Cookies
假设总共吃了 \(k\) 次(假设最后剩下的饼干也被吃掉,此时 \(X\) 就是剩下的饼干数量),其中第 \(i\) 次吃之前额外做了 \(t_i\) 秒的饼干。发现第 \(1\) 次吃时有 \(X=t_1\),第二次吃时有 \(X=t_1\times t_2\),第三次吃时有 \(X=t_1t_2\times t_3\)。所以有 \(X=\prod t_i\)。总时间是 \(\sum t_i+(k-1)a\)。
考虑每次吃 \(X\) 至少翻一倍,所以 \(k\) 不会超过 \(60\)。考虑枚举 \(k\),令 \(C=(k-1)a\),根据基本不等式有:
等号成立条件是 \(t_i\) 全部相等且 \(\prod t_i=n\)。
显然当 \(t_i\in \mathbb{Z}^+\) 时,\(t_i\in\left\{\lfloor\sqrt[k]{n}\rfloor,\lceil\sqrt[k]{n}\rceil\right\}\) 时最优。枚举有多少个选 \(\lfloor\sqrt[k]{n}\rfloor\) 即可。
复杂度 \(O(\log^2 n)\sim O(\log^3 n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100010
#define ll long long
using namespace std;
int main()
{
ll n,a;scanf("%lld%lld",&n,&a);
ll ans=1e18;
for(int i=1;i<=60;i++)
{
ll x=floor(pow(n,1.0/i));
for(int j=0;j<=i;j++)
{
ll s=1;
for(int k=1;k<=i-j;k++) s*=x;
for(int k=i-j+1;k<=i;k++) s*=(x+1);
if(s>=n){ans=min(ans,x*(i-j)+(x+1)*j+(i-1)*a); break;}
}
}
printf("%lld\n",ans);
return 0;
}
F. Road of the King
很妙的题。
首先因为这条路径是从 \(1\) 出发的,所以 \(1\) 必然能到路径经过的所有点。所以只需要所有点能到 \(1\) 即强连通。
容易发现如果一个点和 \(1\) 强连通,那么这个点与 \(1\) 号点可以认为是等价的。所以我们只需要知道现在是否在强连通分量里即可。
同样,一个不包含 \(1\) 的子强连通分量也不必记录,因为路径中前者一定能到达后者,而如果存在某一刻连到 \(1\) 所在的连通分量中,那么所有路径上的点都并入 \(1\) 所在的强连通分量。
考虑 dp。用 \(f_{i,j,k}\) 表示当前路径长度为 \(i\),强连通分量大小为 \(j\),被访问过但是没有在连通分量的点数为 \(k\) 的方案数。
转移考虑枚举下一个点是哪一类点。复杂度 \(O(n^3)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 310
#define mod 1000000007
using namespace std;
int f[N][N][N];
void add(int &u,int v){u=(u+v>=mod?u+v-mod:u+v);}
int main()
{
int n,m;scanf("%d%d",&n,&m);
f[0][1][0]=1;
for(int i=0;i<m;i++)
for(int j=1;j<=i+1;j++)
for(int k=0;j+k<=i+1;k++) if(f[i][j][k])
{
add(f[i+1][j][k+1],1ll*f[i][j][k]*(n-j-k)%mod);
add(f[i+1][j][k],1ll*f[i][j][k]*k%mod);
add(f[i+1][j+k][0],1ll*f[i][j][k]*j%mod);
}
printf("%d\n",f[m][n][0]);
return 0;
}
G. Zigzag MST
观察 kruskal,可以发现 \(z\) 边一定会在 \(z+1\) 边加入前被尝试加入。换句话说在 \(z+1\) 边被加入时,\(z\) 边两端一定是连通的。
所以原题的连边方式可以看成:\((a,b)\) 连边权为 \(c\),\((a+i-1,a+i)\) 连边权为 \(c+2i-1\),\((b+i-1,b+i)\) 连边权为 \(c+2i\)。
这样除了 \((a,b)\) 边,剩下的都是在 \((i,i+1)\) 之间的边,对两端相同的边取最小值加入生成树即可。
复杂度 \(O(m\log m)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400010
using namespace std;
int g[N],f[N];
struct road{
int x,y,w;
}r[N];
int find(int x){return f[x]==x?f[x]:(f[x]=find(f[x]));}
int main()
{
int n,m;scanf("%d%d",&n,&m);
memset(g,0x3f,sizeof(g));
for(int i=0;i<n;i++) f[i]=i;
for(int i=1;i<=m;i++)
{
int x,y,w;scanf("%d%d%d",&x,&y,&w);
r[i]=(road){x,y,w};
g[x]=min(g[x],w+1);
g[y]=min(g[y],w+2);
}
for(int i=0;i<n*2;i++) g[(i+1)%n]=min(g[(i+1)%n],g[i%n]+2);
for(int i=0;i<n;i++) r[++m]=(road){i,(i+1)%n,g[i]};
sort(r+1,r+m+1,[&](road a,road b){return a.w<b.w;});
long long ans=0;
for(int i=1;i<=m;i++)
{
int x=find(r[i].x),y=find(r[i].y);
if(x==y) continue;
f[x]=y;
ans+=r[i].w;
}
printf("%lld\n",ans);
return 0;
}
H. Tokaido
首先对于 \(M=1\) 的部分。注意到因为所有格子权值都是非负数,所以如果当前行动的人棋子所在格子的下一格不是对手的棋子,它一定会往下一格走。
考虑 dp,用 \(f_i\) 表示当前对手在 \(i\) ,自己在 \(i-1\) 时自己最大分数减对手最大分数。用 \(s_i\) 表示 \(\sum_{j\leq i} a_j\),有 \(\displaystyle f_i=\max_{j>i}\left(a_j-s_{j-1}-f_j\right)+s_i\)。
\(\max\) 内部分与 \(i\) 无关,直接维护当前最大值即可。复杂度 \(O(n)\)。
对于 \(M>1\)。不妨换种角度考虑:对手棋子在 \(i\),自己棋子在 \(i+1\)。如果这一步移到 \(i+1\),相当于取 \(a_{i+1}\) 同时互换先后手。否则等于放弃 \(a_{i+1}\) 同时保留先手权。所以 dp 方程为:\(f_i=|a_{i+1}-f_{i+1}|\)。
考虑维护 \(F(x)\) 表示当最后一个数字为 \(x\) 时的值。显然 \(F(x)\) 图像始终是一条折线。\(f_3(x)=F(x),f_n(x)=x\),然后递推式与 dp 方程类似。
从 \(f_{i+1}(x)\rightarrow f_i(x)\) 不是很好维护。考虑倒过来,即维护 \(g_i(x)\),使得 \(g_i(f_{i}(x))=F(x)\)。显然 \(g_3(x)=x\)。由于 \(f_n(x)=x\),所以 \(g_n(x)=F(x)\)。
推导一下,有:
大力将绝对值拆开,有:
那么 \(g_{i+1}\) 本质上就是将 \(g_i\) 图像向右平移了 \(a_i\) 格,并令 \(x\in[0,a_i),g_{i+1}(x)=g_{i}\left(2a_i-x\right)\)。
直接大力维护,复杂度 \(O(m+\sum a_i)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 1000010
#define ll long long
using namespace std;
int a[N],n,m;ll f[N],s[N];
vector<int>res;
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
int w=s[n-1]-s[2];
res.reserve(w*2);
for(int i=0;i<=w;i++) res.push_back(w-i);
for(int i=3;i<n;i++)
{
int t=res.size()-1;
for(int j=1;j<=a[i];j++) res.push_back(res[t-j]);
}
reverse(res.begin(),res.end());
scanf("%d",&m);
while(m --> 0)
{
int x;scanf("%d",&x);
if(x>w) printf("%d\n",x-w+a[1]-a[2]);
else printf("%d\n",res[x]+a[1]-a[2]);
}
return 0;
}
I. Reverse Grid
首先如果 \(n\) 是奇数,那么最中间的一行的翻转与其他行无关,特殊处理。\(m\) 同理。不妨假设 \(n,m\) 都是偶数。
考虑矩阵中的每一个格子只能出现在 \(4\) 个位置上,不妨把这四个位置拿出来,要使得矩阵的其他位置不交换,只能令行交换和列交换次数都是偶数。容易发现:如果四个位置字母互不相同,这样可以使这四个位置在逆序对奇偶性不变下任意排列,即 \(12\) 种方案;如果存在字母相同,因为可以任意交换相同的字母,所以方案数就是四个字母的排列。
但上面考虑的都是所有行列交换次数都为偶数的情况。由于第 \(i\) 行和第 \(n-i+1\) 行的等价的,不妨只考虑 \(i\leq \frac n2\) 的行与 \(j\leq \frac m2\) 的列。容易发现如果存在点 \((i,j)\) 对应四个字母互不相同,\(i\) 行翻转或者 \(j\) 列翻转之后,\((i,j)\) 的奇偶性都会改变。
对行列分别建二分图,每个点点权 \(\in\{0,1\}\),相当于问有多少种给边赋点权的方式,使每条边的权值是其两端点权异或和。容易发现对于每个连通块,每个节点点权完全取反。
用并查集,复杂度 \(O(nm)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 210
#define mod 1000000007
using namespace std;
char s[N][N];int c[26];const int fac[]={1,1,2,6,24,120};
int f[N*2],g[N*2],_2[N*2];
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=_2[0]=1;i<=n+m;i++) _2[i]=_2[i-1]*2ll%mod;
int n0=n,m0=m;n/=2,m/=2;
int res=1;
if(n0&1){bool r0=false;for(int i=1;i<=m;i++) if(s[n+1][i]!=s[n+1][m0-i+1]) r0=true;if(r0) res*=2;}
if(m0&1){bool r0=false;for(int i=1;i<=n;i++) if(s[i][m+1]!=s[n0-i+1][m+1]) r0=true;if(r0) res*=2;}
for(int i=1;i<=n+m;i++) f[i]=i;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
memset(c,0,sizeof(c));
c[s[i][j]-'a']++,c[s[n0-i+1][j]-'a']++,c[s[i][m0-j+1]-'a']++,c[s[n0-i+1][m0-j+1]-'a']++;
int r=fac[4];
for(int p=0;p<26;p++) r/=fac[c[p]];
if(r!=24) res=1ll*res*r%mod;
else
{
res=12ll*res%mod;
f[find(i)]=find(j+n);
}
}
for(int i=1;i<=n+m;i++) g[find(i)]++;
for(int i=1;i<=n+m;i++) if(f[i]==i) res=1ll*res*_2[g[i]-1]%mod;
printf("%d",res);
return 0;
}

浙公网安备 33010602011771号