USACO22 JAN 做题记录
Cu
T1
暴力题。
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxk=27;
int ans1,ans2;
int cnt[maxk],ok[4][4];
string s[4],t[4];
int main(){
cin>>s[1]>>s[2]>>s[3]>>t[1]>>t[2]>>t[3];
for(int i=1;i<=3;i++)
for(int j=0;j<3;j++){
if(s[i][j]==t[i][j])
ans1++,ok[i][j]=1;
else cnt[s[i][j]-64]++;
}
for(int i=1;i<=3;i++)
for(int j=0;j<3;j++)
if(ok[i][j]==0&&cnt[t[i][j]-64])
cnt[t[i][j]-64]--,ans2++;
printf("%d\n%d\n",ans1,ans2);
return 0;
}
T2
暴力题。
#include<stdio.h>
#include<iostream>
using namespace std;
int T;
int a[3][5];
int calc(int x,int y){
int win=0,lose=0;
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
win+=a[x][i]>a[y][j],lose+=a[x][i]<a[y][j];
return win>lose;
}
int main(){
scanf("%d",&T);
while(T--){
int ans=0;
scanf("%d%d%d%d%d%d%d%d",&a[0][1],&a[0][2],&a[0][3],&a[0][4],&a[1][1],&a[1][2],&a[1][3],&a[1][4]);
if(calc(0,1)==0)
for(int i=1;i<=4;i++)
swap(a[0][i],a[1][i]);
if(calc(0,1)==0){
puts("no");
continue;
}
for(int i=1;i<=10;i++)
for(int j=i;j<=10;j++)
for(int k=j;k<=10;k++)
for(int l=k;l<=10;l++){
a[2][1]=i,a[2][2]=j,a[2][3]=k,a[2][4]=l;
if(calc(1,2)&&calc(2,0))
ans=1;
}
puts(ans==1? "yes":"no");
}
return 0;
}
T3
好难。
相邻减一相当于把 \(x\) 位置的差分数组加一,\(x-2\) 位置的差分数组减一,还有两种特殊情况,一个是位置 \(3\) 加一,一个是位置 \(n-2\) 减一。
从后往前模拟即可,时间复杂度 \(O(n)\)。
#include<stdio.h>
#include<math.h>
const int maxn=100005;
int T,n;
int a[maxn];
long long b[maxn];
inline int min(int a,int b){
return a<b? a:b;
}
int main(){
scanf("%d",&T);
while(T--){
long long ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
b[i]=a[i]-a[i-1];
int flg=0;
for(int i=n;i>=2;i--){
if(b[i]>0)
if(((n-i)&1)==0)
flg=1;
if(b[i]<0)
b[i-2]+=b[i];
}
for(int i=1;i<=n;i++)
ans+=a[i]-b[1];
if(b[1]<0||b[0]<0||flg)
puts("-1");
else printf("%lld\n",ans);
b[0]=0;
}
return 0;
}
Ag
T1
感觉是全场最困难的题目。
考虑最优的行动是分四段:
先加若干次,然后若干个除(若当前数末尾是一,则先进行一次加),再加若干次,然后再是若干个乘(若目标数末尾是一,则之后进行一次加)。
直接枚举每次操作次数即可,复杂度 \(O(\log n)\)。
#include<stdio.h>
int T,ans;
long long A,B;
int main(){
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&A,&B),ans=10000;
for(int i=0;i<=10;i++)
for(int j=0;j<=60;j++)
for(int k=0;k<=10;k++){
int res=i;
long long now=A+i;
for(int p=1;p<=j;p++){
if(now&1)
now++,res++;
now>>=1,res++;
}
now+=k,res+=k;
int tot=0;
long long tmp=now;
while((tmp<<1)<=B)
tmp<<=1,tot++;
if((B>>tot)!=now)
continue;
for(int p=1;p<=tot;p++){
now<<=1,res++;
if((B>>(tot-p))&1)
now++,res++;
}
if(res<ans)
ans=res;
}
printf("%d\n",ans);
}
return 0;
}
T2
签到题。
考虑满足 \(\forall_{i<k<j} h_k<\min\{h_i,h_j\}\) 的 \((i,j)\) 对在笛卡尔树上的形态是一个点以及其左儿子的右链,或者是其右儿子的左链,直接在树上搜一遍就好了。
复杂度 \(O(n)\)。
#include<stdio.h>
const int maxn=300005;
int n,top;
int h[maxn],lc[maxn],rc[maxn],stk[maxn],totl[maxn],totr[maxn];
long long ans;
long long suml[maxn],sumr[maxn];
void dfs(int x){
if(lc[x]){
dfs(lc[x]),ans+=1ll*totr[lc[x]]*(x+1)-sumr[lc[x]];
totl[x]=totl[lc[x]]+1,suml[x]=suml[lc[x]]+x;
}
else suml[x]=x,totl[x]=1;
if(rc[x]){
dfs(rc[x]),ans+=suml[rc[x]]-1ll*totl[rc[x]]*(x-1);
totr[x]=totr[rc[x]]+1,sumr[x]=sumr[rc[x]]+x;
}
else sumr[x]=x,totr[x]=1;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&h[i]);
int k=top;
while(k>0&&h[stk[k]]<=h[i])
k--;
if(k>0)
rc[stk[k]]=i;
if(k<top)
lc[i]=stk[k+1];
top=k,stk[++top]=i;
}
dfs(stk[1]),printf("%lld\n",ans);
return 0;
}
T3
有一点点困难。
考虑将一只奶牛最喜欢的两种麦片连边,对于每个连通块,其答案的上界一定是点数和边数的 \(\max\),下面给出一种构造:
找这个连通块的一个 dfs 树,找到其一个环(若没有则任意钦定一个单点),先取环上的一条边,那么剩下的边都可以按顺序取。然后从这个环(点)向外搜索出一个基环树(树)的形态即可。
复杂度 \(O(n)\)。
#include<stdio.h>
#include<vector>
#include<queue>
#include<algorithm>
#include<assert.h>
using namespace std;
const int maxn=200005;
int n,m,ans,flg;
int vis[maxn],vvis[maxn],ok[maxn],fa[maxn],rec[maxn],fir[maxn],sec[maxn],dep[maxn],tr[maxn];
vector<int>tmp,V,res,v[maxn],w[maxn];
queue<int>q;
inline void add(int x,int y,int i){
v[x].push_back(y),w[x].push_back(i);
}
void dfs(int x,int last){
fa[x]=last,vis[x]=1,dep[x]=dep[last]+1;
for(int i=0;i<v[x].size();i++){
int y=v[x][i],k=w[x][i];
if(vis[y]==0)
tr[k]=1,rec[y]=k,dfs(y,x);
else if(y!=last&&flg==0){
int recflg=0;
if(dep[x]<dep[y])
swap(x,y),recflg=1;
flg=1,tmp.clear(),V.clear(),V.push_back(y);
for(int j=x;j!=y;j=fa[j])
V.push_back(j),tmp.push_back(rec[j]);
if(fir[w[x][i]]==y)
reverse(tmp.begin(),tmp.end());
ok[k]=1,res.push_back(k);
for(int j=0;j<tmp.size();j++)
ok[tmp[j]]=1,res.push_back(tmp[j]);
if(recflg)
swap(x,y);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<=n;i++)
scanf("%d%d",&x,&y),fir[i]=x,sec[i]=y,add(x,y,i),add(y,x,i);
for(int i=1;i<=m;i++)
if(vis[i]==0){
flg=0,dfs(i,0);
if(flg)
for(int j=0;j<V.size();j++)
vvis[V[j]]=1,q.push(V[j]);
else vvis[i]=0,q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
for(int j=0;j<v[x].size();j++)
if(tr[w[x][j]]&&vvis[v[x][j]]==0){
int y=v[x][j];
vvis[y]=1,q.push(y);
if(ok[w[x][j]]==0)
res.push_back(w[x][j]),ok[w[x][j]]=1;
}
}
}
for(int i=1;i<=n;i++)
if(ok[i]==0)
res.push_back(i);
for(int i=1;i<=m;i++)
vis[i]=0;
for(int i=0;i<res.size();i++){
int x=res[i];
if(vis[fir[x]]==0)
vis[fir[x]]=1,ans++;
else if(vis[sec[x]]==0)
vis[sec[x]]=1,ans++;
}
printf("%d\n",n-ans);
for(int i=0;i<res.size();i++)
printf("%d\n",res[i]);
return 0;
}
Au
T1
太困难了。
时光倒流,相当于一开始所有数相等,你每次给相邻位置加一,每个位置不能超过上界,求能得到多少序列。
假设所有数的初值是固定的(设其为 \(st\)),我们就可以直接 dp,令 \(f_{i,j}\) 表示选到第 \(i\) 个位置,其中 \((i,i+1)\) 选了 \(j\) 次的合法序列数,那么可以得到:
前缀和优化即可做到 \(O(nV)\)。
当 \(n\) 为偶数的时候,\(st\) 可以直接是 \(0\),否则 \(st\) 可以设为 \(0,1,\cdots,\min h_i\) 的任意数。
总时间复杂度 \(O(nV^2)\)。
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=105,maxv=1005,mod=1000000007;
int n,ans,up,mn=100000;
int h[maxn],f[maxn][maxv],g[maxn][maxv];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]),mn=min(mn,h[i]);
h[n+1]=maxv-1;
if(n%2==0)
up=0;
else up=mn;
for(int st=0;st<=up;st++){
memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
for(int i=0;i<=h[1]-st&&i<=h[2]-st;i++)
f[1][i]=1;
for(int i=1;i<n-1;i++){
g[i][0]=f[i][0];
for(int j=1;j<=h[i]-st&&j<=h[i+1]-st;j++)
g[i][j]=(g[i][j-1]+f[i][j])%mod;
for(int k=0;k<=h[i+1]-st&&k<=h[i+2]-st;k++){
int j=min(min(h[i]-st,h[i+1]-st),h[i+1]-st-k);
f[i+1][k]=g[i][j];
}
}
for(int i=0;i<maxv;i++)
ans=(ans+f[n-1][i])%mod;
}
printf("%d\n",ans);
return 0;
}
T2
漏看了一个条件,居然还做出来了。
考虑时光倒流,然后线段树分治,那么我们对于每个数,找到其第一次合法的时间。
我们将每个点拆点,\(A\) 和 \(B\),初始一个点的 \(A\) 和 \(B\) 连边。合并两个并查集先特判是否有一个连通块是合法的,然后将两个并查集的 \(A\) 连边,撤销的时候给边懒删除即可。
当一个点变成 active 之后,我们就将整个连通块遍历一遍,将遍历到的 \(B\) 点统计答案,并将整个连通块清空,这样可以均摊分析,时间复杂度为 \(O(n\log n\alpha(n))\)。
代码有些细节写错了,但是没有把我卡掉,我就懒得改了。
#include<stdio.h>
#include<map>
#include<vector>
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid (l+r>>1)
using namespace std;
const int maxn=1000005;
int n,q,top,tot,e,stp;
int xx[maxn],yy[maxn],es[maxn],et[maxn],dsu[maxn],stk[maxn],sz[maxn],start[maxn],to[maxn],then[maxn],ans[maxn],vis[maxn],mn[maxn],del[maxn];
char c;
map<pair<int,int>,int>mp;
vector<int>edg[maxn],fam[maxn];
inline void add(int x,int y){
then[++e]=start[x],start[x]=e,to[e]=y;
}
void dfs(int x,int v){
if(x<=n)
ans[x]=v;
vis[x]=stp;
for(int i=start[x];i;i=then[i])
if(vis[to[i]]!=stp)
dfs(to[i],v);
start[x]=0;
}
int find(int x){
return dsu[x]==x? x:find(dsu[x]);
}
void merge(int a,int b,int t){
a=find(a),b=find(b);
if(a==b)
return ;
if(sz[a]>sz[b])
swap(a,b);
dsu[a]=b,sz[b]+=sz[a],stk[++top]=a,mn[b]=min(mn[b],mn[a]);
t=max(t,mn[b]);
if(start[n+a]==0&&start[n+b])
stp++,dfs(n+b,t);
if(start[n+b]==0&&start[n+a])
stp++,dfs(n+a,t);
if(start[n+a]&&start[n+b])
add(n+a,n+b),add(n+b,n+a);
}
void cancel(int rec){
while(top>rec)
sz[find(stk[top])]-=sz[stk[top]],dsu[stk[top]]=stk[top],top--;
}
void insert(int l,int r,int now,int L,int R,int v){
if(L<=l&&r<=R){
edg[now].push_back(v);
return ;
}
if(L<=mid)
insert(l,mid,lc(now),L,R,v);
if(mid<R)
insert(mid+1,r,rc(now),L,R,v);
}
void solve(int l,int r,int now){
int rec=top;
if(l==r)
for(int i=0;i<fam[l].size();i++)
stp++,dfs(n+find(fam[l][i]),l);
for(int i=0;i<edg[now].size();i++)
merge(xx[edg[now][i]],yy[edg[now][i]],q-et[edg[now][i]]+1);
if(l!=r)
solve(l,mid,lc(now)),solve(mid+1,r,rc(now));
cancel(rec);
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n*2;i++)
dsu[i]=i,sz[i]=1;
for(int i=1;i<=n;i++)
add(i,n+i),add(n+i,i);
for(int i=1,x,y;i<=q;i++){
for(c=getchar();c!='A'&&c!='D'&&c!='R';c=getchar());
if(c=='D')
scanf("%d",&x),fam[q-(i-1)+1].push_back(x),del[x]=1,mn[x]=n-(i-1)+1;
if(c=='A')
scanf("%d%d",&x,&y),mp[make_pair(x,y)]=++tot,xx[tot]=x,yy[tot]=y,es[tot]=i;
if(c=='R')
scanf("%d",&x),et[x]=i-1,mp[make_pair(xx[x],yy[x])]=0;
}
for(int i=1;i<=n;i++)
if(del[i]==0)
fam[1].push_back(i),mn[i]=0;
for(int i=1;i<=tot;i++){
if(et[i]==0)
et[i]=q;
insert(1,q,1,q-et[i]+1,q-es[i]+1,i);
}
solve(1,q,1);
for(int i=1;i<=n;i++){
if(ans[i]==0)
puts("0");
else printf("%d\n",q-ans[i]+1);
}
return 0;
}
T3
赛场不会。
令题面中 \(j\) 数组为 \(b\) 数组。
首先有一个简单的差分约束:
可以发现 \(k\) 越大一定是越优的,于是我们直接令 \(k=10^9\),设 \(a_i=x_ik+y_i\)(\(0\leqslant y_i<k\))。
上面的差分约束可以转化为:
\(x_i-x_{i-1}\geqslant 0\),若 \(x_i-x_{i-1}=0\),则有 \(y_i-y_{i-1}\geqslant 0\)。
\(x_i-x_{b_i}\geqslant -1\),若 \(x_i-x_{b_i}=-1\),则有 \(y_i-y_{b_i}\geqslant 0\)。
\(x_{b_i+1}-x_i\geqslant 1\),若 \(x_{b_i+1}-x_i=1\),则有 \(y_{b_i+1}-y_i\geqslant 1\)
我们可以考虑先构造出 \(x\),再通过 \(x\) 构造一个差分约束模型,不难发现由于边权是 \(0/-1\) 且没有负环,所以所有环都是零环,缩点+拓扑排序即可解决。
事实上很容易给出一组 \(x\) 的构造:
令变量 \(p=1\),并不断执行以下操作:
设当前轮数为 \(d\),将 \([p,b_p]\) 设为 \(d\),再令 \(p\leftarrow b_p+1\)。
第一个限制和第三个限制显然满足,考虑为什么满足第二个限制:
我们设覆盖位置 \(i\) 的 \(p\) 为 \(p_0\),则由于 \(b_{p_0}\geqslant i\),而 \(b\) 又是递增的,所以有 \(b_{b_{p_0}+1}\geqslant b_i\),也就是至多下一轮 \(b_i\) 就会被覆盖。
时间复杂度 \(O(n)\)。
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
const int maxn=100005,K=1000000000;
int n,top,dfns,tot,es;
int b[maxn],X[maxn],Y[maxn],deg[maxn],stk[maxn],dfn[maxn],low[maxn],vis[maxn],bel[maxn],xx[maxn*3],yy[maxn*3],zz[maxn*3];
queue<int>q;
vector<int>v[maxn],g[maxn];
inline void add(int x,int y,int z){
if(z==0)
v[x].push_back(y);
es++,xx[es]=x,yy[es]=y,zz[es]=z;
}
void tarjan(int x){
dfn[x]=low[x]=++dfns,vis[x]=1,stk[++top]=x;
for(int i=0;i<v[x].size();i++){
int y=v[x][i];
if(dfn[y]==0)
tarjan(y),low[x]=min(low[x],low[y]);
else if(vis[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
int y=0;
tot++;
while(y!=x)
y=stk[top],top--,bel[y]=tot,vis[y]=0;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
int pos=1,d=1;
while(pos<=n){
for(int i=pos;i<=b[pos];i++)
X[i]=d;
pos=b[pos]+1,d++;
}
for(int i=2;i<=n;i++)
if(X[i]==X[i-1])
add(i,i-1,0);
for(int i=1;i<=n;i++){
if(X[i]-X[b[i]]==-1)
add(i,b[i],0);
if(b[i]<n&&X[b[i]+1]-X[i]==1)
add(b[i]+1,i,-1);
}
for(int i=1;i<=n;i++)
if(dfn[i]==0)
tarjan(i);
for(int i=1;i<=es;i++)
if(bel[xx[i]]!=bel[yy[i]])
g[bel[xx[i]]].push_back(i),deg[bel[yy[i]]]++;
for(int i=1;i<=tot;i++)
if(deg[i]==0)
q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<g[x].size();i++){
int k=g[x][i],y=bel[xx[k]]+bel[yy[k]]-x;
deg[y]--,Y[y]=min(Y[y],Y[x]+zz[k]);
if(deg[y]==0)
q.push(y);
}
}
printf("%d\n",K);
for(int i=1;i<=n;i++)
printf("%lld\n",1ll*X[i]*K+Y[bel[i]]);
return 0;
}