AGC010
拾起来板刷 AGC 的事情,发现之前刷的基本全忘干净了。毕竟半年前的事情了。
现在也完全没有板刷 AGC 的状态和实力。一点点起手吧。
这一套之前做的差不多了,先对着代码看看当时的思路。
[AGC010A] Addition
普及题。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
int n,cnt;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
cnt^=(x&1);
}
if(cnt)puts("NO");
else puts("YES");
return 0;
}
[AGC010B] Boxes
听说暴力能过。不过看看正解。
首先每次总和减少 \(\dfrac {n(n-1)}2\),和必须整除这个。然后由于是减等差数列,考虑差分。得到差分数组 \(d\) 后变成一个数减 \(n-1\) 其他数 \(+1\),让你全变成 \(0\)。
设 \(cnt_i\) 为 \(i\) 开头操作次数,\(M\) 为总操作次数。那么
\(cnt_i\) 必须是整数。就这两个条件。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#define int long long
using namespace std;
int n,a[100010];
long long sum;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum+=a[i];
if(sum%(1ll*n*(n+1)/2)!=0){
puts("NO");return 0;
}
long long cnt=sum/(1ll*n*(n+1)/2);
bool jud=false;
a[0]=a[n];
for(int i=n;i>=1;i--){
a[i]=a[i]-a[i-1];
if((cnt-a[i])%n!=0||cnt-a[i]<0){
puts("NO");return 0;
}
}
puts("YES");
return 0;
}
[AGC010C] Cleaning
看不懂原来的代码了,不知道写的什么东西。于是重新搞了一份。
由于每个节点只有两种消掉方式:子树两条链选出来消一个,或者传到父亲上。那么设 \(f_x\) 为 \(x\) 节点能传上去的,\(sum_x\) 为儿子 \(f_v\) 之和,那么第二种方式个数显然是 \(f_x\),第一种是 \(sum_x-f_x\)。即有:
确保 \(0\le f_x\le a_x\) 且 \(\max f_v\le a_x\) 即可。还有根节点的 \(f\) 一定是 \(0\)。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
struct node{
int v,next;
}edge[200010];
int n,t,rt,a[100010],head[100010],d[100010],f[100010],sum[100010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
void dfs(int x,int fa){
if(d[x]==1){
f[x]=a[x];return;
}
int mx=0;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=fa){
dfs(edge[i].v,x);sum[x]+=f[edge[i].v];mx=max(mx,f[edge[i].v]);
}
}
f[x]=(a[x]<<1)-sum[x];
if(f[x]<0||f[x]>a[x]||mx>a[x]){
puts("NO");exit(0);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
d[u]++;d[v]++;
}
if(n==2){printf("%s",a[1]==a[2]?"YES":"NO");return 0;}
for(int i=1;i<=n;i++)if(d[i]!=1){
rt=i;break;
}
dfs(rt,0);
if(f[rt])puts("NO");
else puts("YES");
return 0;
}
[AGC010D] Decrementing
首先 \(\exist a_i=1\) 那白给。考虑没有 \(1\) 的。
除以 \(\gcd\) 这个操作看起来非常的奇怪。然而我们知道上边那个东西 \(\sum a_i-1\) 只需要看奇偶性,于是我们也来分类讨论一下所有数的奇偶性。
首先如果有奇数个偶数,那么随便选一个偶数 \(-1\) 然后交给对手操作,无论怎样所有数的奇偶性都不会变。容易验证这是先手必胜的。
然后如果是偶数个偶数,这时对我们不优。看看奇数。
如果有超过一个奇数,那白给了。但是如果只有一个,可以把这个奇数减掉然后所有数至少减半,可能有情况翻转。显然至多操作 \(O(\log n)\) 次。算上 \(\gcd\) 的一个 \(\log\) ,总共是 \(O(n\log^2n)\)。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,a[100010];
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int od=0;
while(1){
int cnt1=0,cnt2=0,pos;
bool jud=false;
for(int i=1;i<=n;i++){
if(a[i]==1)jud=true;
if(a[i]>1&&(a[i]&1)){
pos=i;cnt1++;
}
else if(!(a[i]&1))cnt2++;
}
if(jud){
od^=(cnt2&1);
puts(od?"First":"Second");
return 0;
}
if(cnt2&1){
if(!od)puts("First");
else puts("Second");
return 0;
}
else{
if(cnt1>1){
if(!od)puts("Second");
else puts("First");
return 0;
}
if(cnt1==1){
a[pos]--;
int d=0;
for(int i=1;i<=n;i++)d=gcd(d,a[i]);
for(int i=1;i<=n;i++)a[i]/=d;
od^=1;
}
}
}
return 0;
}
[AGC010E] Rearranging
一场考试的题。
先手任意排列,后手交换互质的数。那么如果把不能相互交换的数之间连边,这样就形成了若干连通块。每个连通块内部相对顺序是不能动的,从大到小归并一下就是答案。
考虑每个连通块的答案。我们要每个连通块字典序尽量小,那么直接找到最小的一个开始哪个小搜哪个就行了。
思路很精妙,实现很简易。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
int n,a[2010],ind[2010],ans[2010];
priority_queue<int>q;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
vector<int>g[2010],v[2010];
bool vis[2010];
void dfs(int x){
vis[x]=true;
for(int to:g[x]){
if(!vis[to]){
v[x].push_back(to);ind[to]++;
dfs(to);
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(gcd(a[i],a[j])!=1)g[i].push_back(j);
}
}
for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
for(int i=1;i<=n;i++){
if(ind[i]==0)q.push(i);
}
while(!q.empty()){
int x=q.top();q.pop();
ans[++ans[0]]=x;
for(int to:v[x]){
ind[to]--;
if(!ind[to])q.push(to);
}
}
for(int i=1;i<=n;i++)printf("%d ",a[ans[i]]);
return 0;
}
[AGC010F] Tree Game
另一场考试的题。
首先有结论:每次一定是往更小的点走。如果往不小于的点走,对手永远可以走回来。
于是叶子节点必败,然后就递推就可以了。必败当且仅当不存在必败的儿子。可以 \(O(n^2)\) 扫一遍所有点做根。
/*我现在T3还有一堆FWT 罪证确凿
不过accoders确实好贺就是了
如果不考试就学博弈论和网络瘤
*/
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
struct node{
int v,next;
}edge[6010];
int n,t,head[3010],a[3010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
bool dfs(int x,int f){
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
if(a[edge[i].v]<a[x]&&!dfs(edge[i].v,x))return true;
}
}
return false;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
bool jud=false;
for(int i=1;i<=n;i++){
if(dfs(i,0))printf("%d ",i),jud=true;
}
return 0;
}
好多博弈论。