CF Round 1002 题解合集
困难场。
C
重要观察:只有每行的后缀 \(1\) 有用。
考虑归纳证明,设 \(n\) 为一行后缀 \(1\) 的个数。
对于 \(n=1\) 的情况,想要让 \(mex\) 值为 \(2\),必须选择这个后缀,否则其他后缀的和 一定 \(>1\);
对于 \(n>1\) 的情况,在之前,每次选择后缀 \(1\) 都是在再不选就不行的情况下,比如在最后一个时刻选 \(n=1\),在倒数第二个时刻选 \(n=2\) 之类,所以这个时刻也没有选择,只能选后缀 \(1\)。
因此,我们保留每一行的极长后缀 \(1\) 个数,贪心减小若干值最大化 \(mex\) 即可。
具体贪心的过程为如果当前数不存在,那么选择最接近它的大于它的数来减小。
总体复杂度 \(O(nm)\)。
#include<bits/stdc++.h>
using namespace std;
int t,n,a[301][301],sum[301],flag,cnt,ans;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
cnt=0;
for(int j=n;j>=1;j--){
if(a[i][j]!=1) break;
cnt++;
}
sum[cnt]++;
}
for(int i=0;i<=n;i++){
if(sum[i]) continue;
flag=false;
for(int j=i+1;j<=n;j++){
if(sum[j]){
sum[j]--;
flag=true;
break;
}
}
if(!flag){
ans=i;
break;
}
}
cout<<ans<<'\n';
for(int i=0;i<=n;i++) sum[i]=0;
ans=0;
}
return 0;
}
D
考虑想要使得代价为定值,那么一定是走到了图上相同的两点 \((u,u)\),满足 \(u\) 有相同的出点 \(v\)。
因为点数很小,考虑新建一张图,每个点 \((u,v)\),表示在原图第一张图上走到点 \(u\),原图第二张图上走到点 \(v\),然后连边就是枚举 \(u,v\) 的出点直接连。
这样总点数不会超过 \(n^2\),总边数不会超过 \(m^2\),合法的目标点就是上文所说的合法的 \((u,u)\)。
总体复杂度 \((m^2 \log m^2)\)。
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int t,n,s1,s2,x,y,dcnt,m1,m2,ans,id[1001][1001];
bool vis[1001][1001],chk[1001];
vector<int> v[1001],g[1001];
int head[1000001],nxt[4000001],targetx[4000001],targetw[4000001],tot;
void add(int x,int y,int w){
tot++;
nxt[tot]=head[x];
head[x]=tot;
targetx[tot]=y;
targetw[tot]=w;
}
struct node{
int x,dis;
bool operator <(const node &a)const{
return a.dis<dis;
}
};
priority_queue<node> q;
int dis[1000001],ok[1000001];
void dij(int s){
for(int i=1;i<=dcnt;i++){
dis[i]=inf;
ok[i]=0;
}
dis[s]=0;
q.push((node){s,dis[s]});
while(q.size()){
int x=q.top().x;
q.pop();
if(ok[x]) continue;
ok[x]=1;
for(int i=head[x];i;i=nxt[i]){
int y=targetx[i],w=targetw[i];
if(dis[y]>dis[x]+w){
dis[y]=dis[x]+w;
q.push((node){y,dis[y]});
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--){
cin>>n>>s1>>s2;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
id[i][j]=++dcnt;
}
}
cin>>m1;
for(int i=1;i<=m1;i++){
cin>>x>>y;
vis[x][y]=1;
vis[y][x]=1;
v[x].push_back(y);
v[y].push_back(x);
}
cin>>m2;
for(int i=1;i<=m2;i++){
cin>>x>>y;
if(vis[x][y]) chk[x]=chk[y]=1;
g[x].push_back(y);
g[y].push_back(x);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int l1=0;l1<v[i].size();l1++){
for(int l2=0;l2<g[j].size();l2++){
x=v[i][l1],y=g[j][l2];
add(id[i][j],id[x][y],abs(x-y));
}
}
}
}
dij(id[s1][s2]);
ans=inf;
for(int i=1;i<=n;i++){
if(chk[i]) ans=min(ans,dis[id[i][i]]);
}
if(ans<inf) cout<<ans<<'\n';
else cout<<-1<<'\n';
for(int i=1;i<=n;i++){
v[i].clear();
g[i].clear();
chk[i]=0;
}
for(int i=1;i<=dcnt;i++){
head[i]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
vis[i][j]=0;
}
}
tot=dcnt=0;
}
return 0;
}
E1
困难题。
考虑将矩形摊成序列上,那么相当于我们可以在 \(m\) 个位置加数,并且在 \(m\) 个位置删数。
考虑最小化操作次数,一定是最大化不动的点的个数,并且这些不动的点,构成了 \(a\) 的前缀与 \(b\) 的子序列。
从小到大枚举 \(i\),设 \(c_i\) 为 \(a_i\) 在 \(b\) 中的匹配位置,考虑前缀 \([1,i]\) 是否合法。
-
如果 \(b\) 中不存在 \(i\),死;
-
如果 \(c_i-i < c_{i-1}-(i-1)\) 死;
-
如果 \(c_i-i > c_{i-1}-(i-1)\) 并且 \(i-1\) 不是块的右端点,死。
如果 \(i\) 死了,那么有可能整个块都死了。
所以考虑如果块的左端点到匹配位置的距离比 \(i\) 到右端点的距离更小,那么整个块都死了。
否则只有 \(i\) 这个点会死。
做完了,总体复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
int t,n,m,a[300005],b[300005],pos[600005],bel[300005],flag,ans;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--){
cin>>n>>m;
n*=m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
pos[b[i]]=i;
}
for(int i=1;i<=n;i++){
bel[i]=(i-1)/m+1;
}
for(int i=1;i<=n;i++){
if(!pos[a[i]]) flag=true;
if(i>=2 && pos[a[i]]-i<pos[a[i-1]]-(i-1)) flag=true;
if(i>=2 && pos[a[i]]-i>pos[a[i-1]]-(i-1) && pos[a[i-1]]<m*bel[i-1]) flag=true;
if(flag){
ans=true;
int p=(bel[i]-1)*m+1;
if(p+m-i-1>=pos[a[p]]-p) cout<<n-p+1<<'\n';
else cout<<n-i+1<<'\n';
break;
}
}
if(!ans) cout<<0<<'\n';
ans=flag=0;
for(int i=1;i<=n;i++){
pos[b[i]]=0;
}
}
return 0;
}

浙公网安备 33010602011771号