Codeforces Round 1047 Div.3 DEFG补题
这场有点简单,是我出最多题的一次…
D 模拟
这扯不扯,vp时D题AC了,临结束时又给我重测一遍,又T了。
const int N=1e6+10,mod=998244353;
void solve()
{int n;cin>>n;int cnt=1,sm=0;vector<vector<int>>a(n+1);vector<int>b(n+1),m(n+1,0),num(n+1,0);forr(i,1,n){cin>>b[i];if(m[b[i]]%b[i]==0){sm+=b[i];num[b[i]]=cnt++;}m[b[i]]++;a[b[i]].push_back(num[b[i]]);}if(sm!=n)return cout<<-1<<endl,void();vector<int>id(n+1,0);forr(i,1,n){cout<<a[b[i]][id[b[i]]++]<<' ';// a[b[i]].erase(a[b[i]].begin());TLE的罪魁祸首,erase时间复杂度O(n)}cout<<endl;
}
E 暴力
void solve()
{int n,k;cin>>n>>k;vector<int>a(n+1);forr(i,1,n){cin>>a[i];}int mex=0;//发现在操作一定次数后会循环变化if(k>3){//奇数if(k&1)k=3;else k=4;}forr(x,1,k){vector<int>cnt(n+1,0);forr(i,1,n)cnt[a[i]]++;forr(i,0,n){//求本次mexif(cnt[i]==0){mex=i;break;}}forr(i,1,n){if(cnt[a[i]]>1)a[i]=mex;//有多个 删去自己 mex不会变else if(a[i]>=mex)a[i]=mex;}// forr(i,1,n)cout<<a[i]<<' ';cout<<endl;}int sm=0;forr(i,1,n){// cout<<a[i]<<" ";sm+=a[i];}cout<<endl;cout<<sm<<endl;}
F 线段树
思路来源
需要得到每段数组的前缀最大值,暴力一段段找超时,使用线段树mxmxmx维护,找每个数在子数组中的贡献。
在aia_iai很大的地方,出现新的前缀最大值,ai==bia_i==b_iai==bi时才有贡献
在aia_iai小的地方,有前面的前缀最大值mx[j]mx[j]mx[j]挡着,zi=biz_i=b_izi=bi,对每个包含iii位置的数组会有1的贡献
const int N=2e5+10,mod=998244353,inf=1e15,M=25;
int n;
struct SegmentTree//维护区间最大值
{int tr[N<<2],tag[N<<2];//初始化void build(int pos,int l,int r){tr[pos]=tag[pos]=0;if(l==r)return;int mid=(l+r)>>1;build(pos<<1,l,mid);build(pos<<1|1,mid+1,r);}//下推void pushdown(int pos){tr[pos<<1]=max(tr[pos<<1],tag[pos]);tr[pos<<1|1]=max(tr[pos<<1|1],tag[pos]);tag[pos<<1]=max(tag[pos<<1],tag[pos]);tag[pos<<1|1]=max(tag[pos<<1|1],tag[pos]);tag[pos]=0;}void update(int pos,int l,int r,int al,int ar,int val){if(l!=r)pushdown(pos);if(l>=al&&r<=ar){tr[pos]=max(tr[pos],val);tag[pos]=max(tag[pos],val);//把val下推到子区间return;}int mid=(l+r)>>1;if(al<=mid)update(pos<<1,l,mid,al,ar,val);if(ar>mid)update(pos<<1|1,mid+1,r,al,ar,val);tr[pos]=max(tr[pos<<1],tr[pos<<1|1]);}int query(int pos,int l,int r,int val){if(l==r)return l;pushdown(pos);int mid=(l+r)>>1;if(tr[pos<<1|1]>=val)return query(pos<<1|1,mid+1,r,val);//越往后mx越小return query(pos<<1,l,mid,val);}
}mx;//mx[j]是以j为左端点的区间最大值void solve()
{int ans=0;cin>>n;vector<int>a(n+1),b(n+1);forr(i,1,n)cin>>a[i];forr(i,1,n)cin>>b[i];mx.build(1,1,n);mx.update(1,1,n,1,1,a[1]);if(a[1]==b[1])ans+=n;//值一样 在n个数组中有贡献forr(i,2,n){int aj;if(mx.tr[1]<a[i])aj=0;//a[i]比前面的都大else aj=mx.query(1,1,n,a[i]);//最近的mx[j]大于等于a[i]的// 找a[i]<=mx[j]的位置贡献int bj;if(mx.tr[1]<b[i])bj=0;else bj=mx.query(1,1,n,b[i]);//最近的mx[j]大于等于b[i]的ans+=min(aj,bj)*(n-i+1);//前面能到min(aj,bj)以及更前面的地方 有max(mx[aj],mx[bj])挡着// 找a[i]>mx[j]的 a[i]是新的前缀最大值ans+=(a[i]==b[i])*(i-aj)*(n-i+1);// cout<<aj<<' '<<bj<<endl;mx.update(1,1,n,1,i,a[i]);}cout<<ans<<endl;
}
G 图 逆向思维 博弈
哭我一条河
思路来源
遇到红的点,后手River必赢;因为是无向图,一路上没遇到红点就能到一个没有出边的节点让Cry获胜。当点上一个红点时,要考虑哪个点之后能到红点,逆向思维考虑就是红点能延伸到什么地方。所以建立反向图,先手从u点走能到红点的Cry就输了。
const int N=2e5+10,mod=998244353,inf=1e15,M=25;
vector<int>e[N];
int deg[N],dp[N][2];
//从变红的点开始,按先手后手向外扩散 st=0先手,st=1后手
void dfs(int nw,int st){if(dp[nw][st])return;dp[nw][st]=1;//有标记的地方都是最后能走到红点的地方for(auto x:e[nw]){if(!st)dfs(x,1);//在这个点先手else if(--deg[x]==0)//后手 遇到红点能到的就--deg 如果减到0 那么x连的都是红点 下一步必然是River赢dfs(x,0);}
}
void solve()
{int n,m,q;cin>>n>>m>>q;forr(i,1,n){e[i].clear();deg[i]=0;dp[i][0]=dp[i][1]=0;}forr(i,1,m){int u,v;cin>>u>>v;//u指向ve[v].push_back(u);//反向边deg[u]++;//出度 反向边入度}forr(i,1,q){int op,u;cin>>op>>u;if(op==1){//任意一人到红点Cry就输了,所以先后手都从u走一遍dfs(u,0);dfs(u,1);}else{if(!dp[u][0])cout<<"YES"<<endl;else cout<<"NO"<<endl;}}
}