CODE FESTIVAL 2016 Final
链接
D. Pair Cards
对于 统计有多少对相同的数 ,以及有多少数 。考虑贪心匹配,首先 和 直接自己匹配,对于 ,将它与 匹配,优先将散的匹配对面的,然后再用一对的匹配,如果匹配光了就自己匹配。
正确性显然。复杂度 。
代码
#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
假设总共吃了 次(假设最后剩下的饼干也被吃掉,此时 就是剩下的饼干数量),其中第 次吃之前额外做了 秒的饼干。发现第 次吃时有 ,第二次吃时有 ,第三次吃时有 。所以有 。总时间是 。
考虑每次吃 至少翻一倍,所以 不会超过 。考虑枚举 ,令 ,根据基本不等式有:
等号成立条件是 全部相等且 。
显然当 时, 时最优。枚举有多少个选 即可。
复杂度 。
代码
#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
很妙的题。
首先因为这条路径是从 出发的,所以 必然能到路径经过的所有点。所以只需要所有点能到 即强连通。
容易发现如果一个点和 强连通,那么这个点与 号点可以认为是等价的。所以我们只需要知道现在是否在强连通分量里即可。
同样,一个不包含 的子强连通分量也不必记录,因为路径中前者一定能到达后者,而如果存在某一刻连到 所在的连通分量中,那么所有路径上的点都并入 所在的强连通分量。
考虑 dp。用 表示当前路径长度为 ,强连通分量大小为 ,被访问过但是没有在连通分量的点数为 的方案数。
转移考虑枚举下一个点是哪一类点。复杂度 。
代码
#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,可以发现 边一定会在 边加入前被尝试加入。换句话说在 边被加入时, 边两端一定是连通的。
所以原题的连边方式可以看成: 连边权为 , 连边权为 , 连边权为 。
这样除了 边,剩下的都是在 之间的边,对两端相同的边取最小值加入生成树即可。
复杂度 。
代码
#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
首先对于 的部分。注意到因为所有格子权值都是非负数,所以如果当前行动的人棋子所在格子的下一格不是对手的棋子,它一定会往下一格走。
考虑 dp,用 表示当前对手在 ,自己在 时自己最大分数减对手最大分数。用 表示 ,有 。
内部分与 无关,直接维护当前最大值即可。复杂度 。
对于 。不妨换种角度考虑:对手棋子在 ,自己棋子在 。如果这一步移到 ,相当于取 同时互换先后手。否则等于放弃 同时保留先手权。所以 dp 方程为:。
考虑维护 表示当最后一个数字为 时的值。显然 图像始终是一条折线。,然后递推式与 dp 方程类似。
从 不是很好维护。考虑倒过来,即维护 ,使得 。显然 。由于 ,所以 。
推导一下,有:
大力将绝对值拆开,有:
那么 本质上就是将 图像向右平移了 格,并令 。
直接大力维护,复杂度 。
代码
#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
首先如果 是奇数,那么最中间的一行的翻转与其他行无关,特殊处理。 同理。不妨假设 都是偶数。
考虑矩阵中的每一个格子只能出现在 个位置上,不妨把这四个位置拿出来,要使得矩阵的其他位置不交换,只能令行交换和列交换次数都是偶数。容易发现:如果四个位置字母互不相同,这样可以使这四个位置在逆序对奇偶性不变下任意排列,即 种方案;如果存在字母相同,因为可以任意交换相同的字母,所以方案数就是四个字母的排列。
但上面考虑的都是所有行列交换次数都为偶数的情况。由于第 行和第 行的等价的,不妨只考虑 的行与 的列。容易发现如果存在点 对应四个字母互不相同, 行翻转或者 列翻转之后, 的奇偶性都会改变。
对行列分别建二分图,每个点点权 ,相当于问有多少种给边赋点权的方式,使每条边的权值是其两端点权异或和。容易发现对于每个连通块,每个节点点权完全取反。
用并查集,复杂度 。
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理