题目链接:https://codeforces.com/gym/102832
题解:https://zhuanlan.zhihu.com/p/279287505
A. Krypton
分析
除奖励外,其它的倍率均为 (10),因此只要求出奖励的最大值即可,直接 (0/1) 背包。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2010;
int dp[9][N];
int a[8]={0,8,18,28,58,128,198,388};
int cost[8]={0,1,6,28,88,198,328,648};
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=7;i++)
{
for(int j=1;j<=n;j++)
{
if(j>=cost[i])
dp[i][j]=max(dp[i-1][j-cost[i]]+a[i],dp[i-1][j]);
else
dp[i][j]=dp[i-1][j];
}
}
printf("%d
",n*10+dp[7][n]);
return 0;
}
E. Defense of Valor League
分析
对抗博弈。
代码
H. Combination Lock
分析
二分图博弈模板题,每次变换必然会导致当前数字的数位之和的奇偶性改变,按照奇偶性将点分为两类。其中源点连偶数点,汇点连奇数点,偶数点连奇数点,同时避免不能出现的数。先不把起始点加入,跑一遍,再把起始点加入,跑一遍,通过判断第二遍的匹配数是否为 (0) 来判断胜者。
代码
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
//关键在于判断起始状态是否是最大匹配的必须点
//先求一遍最大匹配,然后把起点删除再求一遍最大匹配,比较两次的结果
const int N=1e5+100;
const int inf=0x3f3f3f3f;
int vis[N];
int fac[6];
struct node
{
int to,val,rev;
};
vector<node>pic[N];
queue<int>que;
int layer[N],iter[N],maxn,st,n,m,start,eds;
void read(int &x)
{
x=0;
int f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
x*=f;
}
int sum(int x)
{
int res=0;
while(x)
{
res+=(x%10);
x/=10;
}
return res;
}
void addedge(int u,int v,int w)
{
pic[u].pb(node{v,w,pic[v].size()});
pic[v].pb(node{u,0,pic[u].size()-1});
}
void init(int flag)
{
for(int i=0;i<=eds;i++)
pic[i].clear();
for(int i=0;i<=maxn;i++)
{
if(vis[i]==flag) continue;
if(sum(i)&1)
{
if(i!=st) addedge(i,eds,1);
}
else
{
if(i!=st) addedge(start,i,1);
for(int j=1;j<=m;j++)
{//偶数向奇数连边,偶数连源点,奇数连汇点
int y=i,x=i;
y/=fac[j-1];
y%=10;//写错了,debug好久...
x-=y*fac[j-1];
int t=(y+1)%10;
int d=x+fac[j-1]*t;
addedge(i,d,1);
t=(y-1+10)%10;
d=x+fac[j-1]*t;
addedge(i,d,1);
}
}
}
}
bool bfs()
{
while(!que.empty())
que.pop();
for(int i=0;i<=eds;i++)
layer[i]=-1;
layer[start]=0;
que.push(start);
while(!que.empty())
{
int now=que.front();
que.pop();
for(auto tmp:pic[now])
{
if(layer[tmp.to]<0&&tmp.val>0)
{
layer[tmp.to]=layer[now]+1;
que.push(tmp.to);
if(tmp.to==eds)
return true;
}
}
}
return false;
}
int dfs(int u,int w)
{
if(u==eds||w==0)
return w;
for(int &i=iter[u];i<pic[u].size();i++)
{
node &tmp=pic[u][i];
if(layer[tmp.to]>layer[u]&&tmp.val>0)
{
int d=dfs(tmp.to,min(w,tmp.val));
if(d>0)
{
tmp.val-=d;
pic[tmp.to][tmp.rev].val+=d;
return d;
}
}
}
return 0;
}
int dinic()
{
int max_flow=0;
while(bfs())
{
for(int i=0;i<=eds;i++)
iter[i]=0;
int d=0;
while((d=dfs(start,inf))>0)
max_flow+=d;
}
return max_flow;
}
int main()
{
int T;
read(T);
fac[0]=1;
for(int i=1;i<=5;i++)
fac[i]=fac[i-1]*10;
while(T--)
{
read(m),read(n),read(st);
maxn=1;//点数
for(int i=1;i<=m;i++) maxn*=10;
maxn--;
start=fac[m];
eds=start+1;
for(int i=1;i<=n;i++)
{
int d;
read(d);
vis[d]=T+1;
}
init(T+1);
int ans=dinic();//先不把起始点加入,跑一遍dinic
if(sum(st)&1) addedge(st,eds,1);
else addedge(start,st,1);
ans=dinic();//再把点加入,看还能不能流
if(ans==0) printf("Bob
");
else printf("Alice
");
}
return 0;
}
K. Ragdoll
分析
对于一个数 (x) ,其和另一个数可能的 (gcd) 的个数为其约数的个数,而且 (gcd) 必然为其约数中的一个。假设 (x) 的一个约数为 (g) ,那么有 (gcd(g,x)=g=x igoplus y),所以 (y=xigoplus g),接下来只要再验证一下 (gcd(x,y)=g) 即可求出与 (x) 满足条件的每个 (y) 的值。这个过程可以借助埃式筛在 (O(nlog^2n)) 的时间内完成。
接下来用 ( ext{unorder_map}) 来维护每棵树中点权的出现次数,每棵树用并查集维护一个树根。在两棵树合并的时候,将小的树向大的树合并,并且修改答案。修改点权时,先将原来的点权的点对减掉,再加上新的点权的贡献。启发式合并,复杂度:(O(nlog n))。
然后,就是二维 ( ext{unorder_map}) 的用法。
代码
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+5;
int a[N*3],fa[N*3];
unordered_map<int,int>mp[N*3];
vector<int>num[N*2];
ll ans;
int gcd(int x,int y)
{
return y?gcd(y,x%y):x;
}
void init()
{
int maxn=2e5;//埃式筛预处理出每个数的因数,并判断是否有满足条件的数对
for(int i=1;i<=maxn;i++)
{
for(int j=i+i;j<=maxn;j+=i)
{
int t=(i^j);
if(t>0&&t<=maxn&&gcd(t,j)==i)
num[j].pb(t);
}
}
}
int Find(int x)//并查集维护是否在同一颗树上
{
if(x!=fa[x])
return fa[x]=Find(fa[x]);
else return x;
}
void join(int x,int y)
{
int fx=Find(x);
int fy=Find(y);
if(fx==fy) return;
if(mp[fx].size()>mp[fy].size())//默认x为小树,y为大树
swap(fx,fy);
for(auto i:mp[fx])//枚举mp[fx]出现的点权
{
for(auto j:num[i.first])//枚举与mp[fx][i]满足条件的值出现在y中值
{
if(mp[fy].count(j))
ans+=1LL*i.second*mp[fy][j];
}
}
for(auto i:mp[fx])//树的合并
mp[fy][i.first]+=i.second;
fa[fx]=fy;
}
void update(int x,int y)
{
int fx=Find(x);
for(auto i:num[a[x]])
ans-=mp[fx][i];
mp[fx][a[x]]--;
for(auto i:num[y])
ans+=mp[fx][i];
mp[fx][y]++;
a[x]=y;
}
int main()
{
int n,m;
ans=0;
init();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
fa[i]=i;
mp[i][a[i]]=1;
}
int op,x,y;
while(m--)
{
scanf("%d%d%d",&op,&x,&y);
if(op==1)
{
a[x]=y;
fa[x]=x;
mp[x][a[x]]=1;
}
else if(op==2)
join(x,y);
else
update(x,y);
printf("%lld
",ans);
}
return 0;
}
L. Coordinate Paper
分析
首先,只要确定了 (a_i) ,就可以确定 ((sum_{i=1}^{n}{a_i})mod (k+1)) 的值。因为,如果不考虑 (a_i-a_{i+1}=k) 的情况,有:
再考虑 (a_i-a_{i+1}=k) 的情况,就在上式的基础上减去了 (x imes (k+1)(xgeq 0)) 。
即:
因此,对于一个确定的 (a_1\% (k+1)) ,可以在 (O(1)) 的时间内确定 (sum_{i=1}^{n}{a_i}mod (k+1)) 的值,此时的序列一定为如下形式:
此时,对于一个确定的 (a_1) ,该序列有 (minsum_{i=1}^{n}{a_i}) 。
如果满足:
则必然可以构造出满足要求的序列。
因为可以不断地在满足 (sum_{i=1}^{n}{a_i}) 最小的序列中不断的加 ((k+1)) ,使得最后的和为 (S)。加的时候,先对 (0) 加,不够再对 (1) 加,以此类推,因为要满足题目给的相邻两个数的大小条件。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
ll a[N];
int num[N];
int main()
{
int n,k,f=0;
ll s,tmp=0;
scanf("%d%d%lld",&n,&k,&s);
if(n==1)
{
printf("%lld
",s);
return 0;
}
for(int i=0;i<=k;i++)
{
ll d=1LL*k*(k+1)/2;
ll m=(n-(k-i+1))/(k+1);//完整区间的个数
int x=n-(k-i+1)-m*(k+1);//后面剩余部分的个数
tmp=1LL*(i+min(k,i+n-1))*(min(k,i+n-1)-i+1)/2+d*m;
if(x>0)
tmp+=1LL*(0+x-1)*x/2;
if(s>=tmp&&s%(k+1)==tmp%(k+1))
{
f=1;
a[1]=i;
break;
}
}
if(f==0)
printf("-1
");
else
{
num[a[1]]++;
for(int i=2;i<=n;i++)
{
a[i]=(a[i-1]+1)%(k+1);
num[a[i]]++;
}
ll res=0,y=(s-tmp)/(k+1);
ll r=y%n,w=y/n;
int p=-1;
for(int i=0;i<=k;i++)
{
res+=num[i];
if(res>r)
{
p=i;
res=r-(res-num[i]);
break;
}
}
for(int i=1;i<=n;i++)
{
if(a[i]<p) a[i]+=1LL*(k+1)*(w+1);
else if(a[i]>p) a[i]+=1LL*(k+1)*w;
else if(a[i]==p)
{
if(res>0) a[i]+=1LL*(k+1)*(w+1),res--;
else a[i]+=1LL*(k+1)*w;
}
}
for(int i=1;i<=n;i++)
printf("%lld%c",a[i],i==n?'
':' ');
}
return 0;
}
//10 2 55
//2 10 20