总结
第一次打cf的edu round,发现是acm赛制,感觉比平常的cf赛制要好玩一些。这场的题目比较水,当然我没打过其他场不能做比较。然而因为时间不太够所以并没有AK,果然自己跟Claris等神牛还是有很大差距的。下面附上我的成绩:
这才刚结束,还没有hack完,所以感觉应该还能再升几名。
upd:果然吧自己给奶死了呀。第二天早上起来就被hack了。就是因为E题数据开小了一丢丢。。。
题解
A. Garden
送分题,怎么做都行。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int n,k;
int main()
{
scanf("%d%d",&n,&k);
int ans=k;
for (int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
if (k%x==0) ans=min(ans,k/x);
}
printf("%d",ans);
return 0;
}
B. Browser
也是送分题,不过关于细节的讨论有一点多。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int n,p,l,r;
int main()
{
scanf("%d%d%d%d",&n,&p,&l,&r);
int ans;
if (p<l)
{
if (r<n) ans=r-p+2;
else ans=l-p+1;
}
else if (p>r)
{
if (l>1) ans=p-l+2;
else ans=p-r+1;
}
else
{
if (l>1&&r<n) ans=r-l+2+min(r-p,p-l);
else if (l>1) ans=p-l+1;
else if (r<n) ans=r-p+1;
else ans=0;
}
printf("%d",ans);
return 0;
}
C. Permute Digits
在这题卡了一会,交了4次才过。可以暴力枚举前多少位相同然后判断后面能否严格小于剩下的贪心即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
int a[25],b[25],t[15],b1,a1;
bool check(int x)
{
for (int i=x-1;i>=0;i--) if (t[i]) return 1;
return 0;
}
void pri(int w)
{
for (int i=b1;i>=w;i--) putchar(b[i]+'0');
for (int i=b[w-1]-1;i>=0;i--)
if (t[i]) {putchar(i+'0');t[i]--;break;}
for (int i=9;i>=0;i--)
for (int j=1;j<=t[i];j++)
putchar(i+'0');
}
int main()
{
LL x,y;
scanf("%I64d%I64d",&x,&y);
while (x) a[++a1]=x%10,x/=10,t[a[a1]]++;
while (y) b[++b1]=y%10,y/=10;
if (a1<b1)
{
for (int i=1;i<=a1;i++)
for (int j=9;j>=0;j--)
if (t[j]) {putchar(j+'0');t[j]--;break;}
}
else
{
int w=1;
for (int i=b1;i>=1;i--)
if (t[b[i]]) t[b[i]]--;
else {w=i+1;break;}
if (w==1) pri(1);
else
{
while (!check(b[w-1])) t[b[w]]++,w++;
pri(w);
}
}
return 0;
}
D. Almost Acyclic Graph
刚开始看的时候没什么想法。做完E之后回来想了一下,发现只要随便扒一个简单环下来,如果答案存在则一定在该环上面。因为是简单环所以边数不超过n,暴力枚举删边然后拓扑排序检验即可。我扒环用的是tarjan。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505;
int n,m,cnt,last[N],tim,dfn[N],low[N],deg[N],a[N],top,stack[N],flag,col[N],tot;
struct edge{int to,next,from;bool del;}e[100005];
bool ins[N];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void addedge(int u,int v)
{
e[++cnt].from=u;e[cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
}
void tarjan(int x)
{
dfn[x]=low[x]=++tim;
stack[++top]=x;ins[x]=1;
for (int i=last[x];i;i=e[i].next)
if (!dfn[e[i].to])
{
tarjan(e[i].to);
low[x]=min(low[x],low[e[i].to]);
}
else if (ins[e[i].to])
{
low[x]=min(low[x],dfn[e[i].to]);
if (flag) continue;
for (int j=top;stack[j]!=e[i].to;j--) col[stack[j]]=++tot;
col[e[i].to]=++tot;
flag=1;
}
if (dfn[x]==low[x])
{
while (stack[top]!=x) ins[stack[top]]=0,top--;
ins[x]=0;top--;
}
}
bool check()
{
memset(deg,0,sizeof(deg));
for (int i=1;i<=m;i++) if (!e[i].del) deg[e[i].to]++;
int a1=0;
for (int i=1;i<=n;i++) if (!deg[i]) a[++a1]=i;
for (int i=1;i<=a1;i++)
for (int j=last[a[i]];j;j=e[j].next)
{
if (e[j].del) continue;
deg[e[j].to]--;
if (!deg[e[j].to]) a[++a1]=e[j].to;
}
return a1==n;
}
int main()
{
n=read();m=read();
for (int i=1;i<=m;i++)
{
int x=read(),y=read();
addedge(x,y);
}
for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
if (!flag) {puts("YES");return 0;}
for (int i=1;i<=m;i++)
if (col[e[i].from]&&col[e[i].to]&&col[e[i].from]==col[e[i].to]+1||col[e[i].from]==1&&col[e[i].to]==tot)
{
e[i].del=1;
if (check()) {puts("YES");return 0;}
e[i].del=0;
}
puts("NO");
return 0;
}
E. Physical Education Lessons
看完题就发现可以用线段树来做,只要动态开点即可。这样做的空间复杂度较大,幸好cf没有卡空间。。。这题的做法还有很多,比如果ODT(old driver tree)之类的。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300005;
int n,m,sz;
struct tree{int l,r,s,tag;}t[N*50];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void pushdown(int d,int l,int r)
{
if (l==r||t[d].tag==-1) return;
int mid=(l+r)/2;
if (!t[d].l) t[d].l=++sz;
if (!t[d].r) t[d].r=++sz;
if (t[d].tag==0)
{
t[t[d].l].tag=t[t[d].r].tag=0;
t[t[d].l].s=mid-l+1;
t[t[d].r].s=r-mid;
}
else
{
t[t[d].l].tag=t[t[d].r].tag=1;
t[t[d].l].s=t[t[d].r].s=0;
}
t[d].tag=-1;
}
void ins(int &d,int l,int r,int x,int y,int z)
{
if (!d) d=++sz,t[d].tag=-1;
if (t[d].tag==z) return;
pushdown(d,l,r);
if (l==x&&r==y) {t[d].tag=z;t[d].s=!z?r-l+1:0;return;}
int mid=(l+r)/2;
if (y<=mid) ins(t[d].l,l,mid,x,y,z);
else if (x>mid) ins(t[d].r,mid+1,r,x,y,z);
else ins(t[d].l,l,mid,x,mid,z),ins(t[d].r,mid+1,r,mid+1,y,z);
t[d].s=t[t[d].l].s+t[t[d].r].s;
}
int main()
{
n=read();m=read();sz=1;t[1].s=n;t[1].tag=0;
int rt=1;
while (m--)
{
int l=read(),r=read(),op=read();
if (op==1) ins(rt,1,n,l,r,1);
else ins(rt,1,n,l,r,0);
printf("%d\n",t[1].s);
}
return 0;
}
F. Imbalance Value of a Tree
考场上唯一没有A的题。看完题第一想法是点分治,发现无法合并贡献。其实不用那么麻烦。可以把点按权值排序后逐个加入,用并查集来维护每个点作为最大值/最小值时对答案的贡献即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1000005;
int n,f[N],s[N],a[N],last[N],cnt,val[N];
struct edge{int to,next;}e[N*2];
bool vis[N];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void addedge(int u,int v)
{
e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}
bool cmp(int x,int y)
{
return val[x]<val[y];
}
int find(int x)
{
if (f[x]==x) return x;
else return f[x]=find(f[x]);
}
int main()
{
n=read();
for (int i=1;i<=n;i++) val[i]=read();
for (int i=1;i<n;i++)
{
int x=read(),y=read();
addedge(x,y);
}
for (int i=1;i<=n;i++) f[i]=i,s[i]=1,a[i]=i;
sort(a+1,a+n+1,cmp);
LL ans=0;
for (int i=n;i>=1;i--)
{
int x=a[i];vis[x]=1;
for (int j=last[x];j;j=e[j].next)
if (vis[e[j].to])
{
int p=find(x),q=find(e[j].to);
ans-=(LL)s[p]*s[q]*val[x];
s[q]+=s[p];f[p]=q;
}
}
for (int i=1;i<=n;i++) f[i]=i,s[i]=1,vis[i]=0;
for (int i=1;i<=n;i++)
{
int x=a[i];vis[x]=1;
for (int j=last[x];j;j=e[j].next)
if (vis[e[j].to])
{
int p=find(x),q=find(e[j].to);
ans+=(LL)s[p]*s[q]*val[x];
s[q]+=s[p];f[p]=q;
}
}
printf("%I64d",ans);
return 0;
}
G. Coprime Arrays
如果只用回答某个特定的k的话,可以考虑容斥,也就是
这个式子显然可以分块。但现在我们要知道[1,k]的答案,每一个单独算的话显然不行。设ans[k]表示数组范围在[1,k]时的答案,对于每一个i,考虑ans[1..k]里面所有系数为
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=2000005;
const int MOD=1000000007;
int n,k,mu[N],prime[N],tot,ans[N],po[N];
bool not_prime[N];
void get_prime(int n)
{
mu[1]=1;
for (int i=2;i<=n;i++)
{
if (!not_prime[i]) prime[++tot]=i,mu[i]=-1;
for (int j=1;j<=tot&&i*prime[j]<=n;j++)
{
not_prime[i*prime[j]]=1;
if (i%prime[j]==0) break;
mu[i*prime[j]]=-mu[i];
}
}
for (int i=1;i<=n;i++) mu[i]+=mu[i]<0?MOD:0;
}
int ksm(int x,int y)
{
int ans=1;
while (y)
{
if (y&1) ans=(LL)ans*x%MOD;
x=(LL)x*x%MOD;y>>=1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&k);
get_prime(k);
for (int i=1;i<=k;i++) po[i]=ksm(i,n);
for (int i=1;i<=k;i++)
for (int j=i;j<=k;j+=i)
{
int w=(LL)mu[i]*po[j/i]%MOD;
ans[j]+=w;ans[j]-=ans[j]>=MOD?MOD:0;
ans[j+i]+=MOD-w;ans[j+i]-=ans[j+i]>=MOD?MOD:0;
}
for (int i=1;i<=k;i++) ans[i]+=ans[i-1],ans[i]-=ans[i]>=MOD?MOD:0;
int s=0;
for (int i=1;i<=k;i++) s+=ans[i]^i,s-=s>=MOD?MOD:0;
printf("%d",s);
return 0;
}