Codeforces Round #656 (Div. 3)
咋感觉比一般的div3难点/jk
http://codeforces.com/contest/1385/problem/A
http://codeforces.com/contest/1385/problem/B
http://codeforces.com/contest/1385/problem/C
http://codeforces.com/contest/1385/problem/D
前四个简单说吧,A随便做做就行,我写的似乎有点麻烦,B是按照每个数第一次出现的顺序来确定排列
C的话观察一下发现一个数列是“好的”要求存在一个 \(i\),使得 \([1,i]\) 单调不降,\([i,n]\) 单调不升
D是用一个 \(work(l,r,c)\) 来递归的解决 \(s_l\cdots s_r\) 变成一个 c-good 的数组最少要多少步
就用 \(left\) 表示把 \([1,mid]\) 变成 \(c\) 所需步数(就是有几个字符不是 \(c\)),\(right\) 表示 \([mid+1,r]\) 所需步数
那 \(work(l,r,c)=\min(left+work(mid+1,r,c+1),right+work(l,mid,c+1))\)
\(l=r\) 时开始回溯,复杂度 \(O(n\log n)\)
A
int main(){int T=read();while(T--){
int x=read(),y=read(),z=read();
if(x==y&&z<=x) printf("YES\n%d %d %d\n",x,z,1);
else if(x==z&&y<=x) printf("YES\n%d %d %d\n",y,x,1);
else if(y==z&&x<=y) printf("YES\n%d %d %d\n",x,1,y);
else puts("NO");
}
return 0;
}
B
int t[55];
int main(){int T=read();while(T--){
int n=read();
for(reg int x,i=1;i<=n*2;i++){
x=read();
if(!t[x]){
t[x]=1;
printf("%d ",x);
}
}
std::memset(t,0,sizeof t);
EN;
}
return 0;
}
C
int a[200005];
int main(){int T=read();while(T--){
int n=read();
for(reg int i=1;i<=n;i++) a[i]=read();
reg int i=n;
while(a[i]<=a[i-1]&&i>1) i--;
while(a[i]>=a[i-1]&&i>1) i--;
printf("%d\n",i-1);
}
return 0;
}
D
char s[200005];
int work(int l,int r,char c){
if(l==r) return s[l]!=c;
int mid=(l+r)>>1;
int left=0,right=0;
for(reg int i=l;i<=mid;i++) left+=s[i]!=c;
for(reg int i=mid+1;i<=r;i++) right+=s[i]!=c;
return std::min(left+work(mid+1,r,c+1),right+work(l,mid,c+1));
}
int main(){int T=read();while(T--){
int n=read();scanf("%s",s+1);
printf("%d\n",work(1,n,'a'));
}
return 0;
}
CF1385E Directing Edges
http://codeforces.com/contest/1385/problem/E
一个图,有一些边是有向的,有一些还没确定方向
为这些没确定方向的边确定方向,能否使之不存在环。如果能,输出一种方案
比赛时口胡了一个离谱做法,赛后才知道要按拓扑序连边
首先建图的时候只把有向边建进去,判一下有没有环,有的话肯定是不存在
没有的话按照拓扑序,从小到大连边,如果拓扑序相同,则按编号从小到大连边(这是为了防止一堆无向边连成环)
这样,从每个点出发,都只会到达比它拓扑序或编号更大的点,自然就不会回到自身了,也就没有了环
果然这种图中同时有向、无向边的时候,可能还是需要把两种边分开考虑
#define N 200005
#define M 200005
struct graph{
int fir[N],nex[M],to[M],tot,t[M];
inline void add(int u,int v){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
}
}G;
int n,m;
int q[N],tail,head,in[N];
int in_deg[N],f[N];
int x[N],y[N];
int vis[N],instack[N];
int dfs(int u){
vis[u]=instack[u]=1;
for(reg int i=G.fir[u],v;i;i=G.nex[i]){
v=G.to[i];
if(instack[v]) return 0;
if(!vis[v]&&!dfs(v)) return 0;
}
instack[u]=0;
return 1;
}
inline void topo(){
head=-1;tail=0;
for(reg int i=1;i<=n;i++)if(!in_deg[i]) q[++head]=i;
reg int u,v;
while(tail<=head){
u=q[tail++];
for(reg int i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
f[v]=std::max(f[v],f[u]+1);
if(!--in_deg[v]) q[++head]=v;
}
}
}
int main(){int T=read();
while(T--){
n=read();m=read();
for(reg int t,u,v,i=1;i<=m;i++){
t=read();u=read();v=read();
if(t) G.add(u,v),in_deg[v]++;
else x[++x[0]]=u,y[x[0]]=v;
}
int flag=1;
for(reg int i=1;i<=n;i++)if(!vis[i]&&!dfs(i)){flag=0;break;}
if(!flag) puts("NO");
else{
topo();
puts("YES");
// printf("%d %d\n",n,m);
for(reg int i=1;i<=n;i++)
for(reg int j=G.fir[i];j;j=G.nex[j]) printf("%d %d\n",i,G.to[j]);
for(reg int i=1;i<=x[0];i++)
if(f[x[i]]<f[y[i]]||(f[x[i]]==f[y[i]]&&x[i]<y[i])) printf("%d %d\n",x[i],y[i]);
else printf("%d %d\n",y[i],x[i]);
}
for(reg int i=1;i<=n;i++) G.fir[i]=vis[i]=instack[i]=in_deg[i]=0;
G.tot=0;x[0]=0;
}
return 0;
}
CF1385F Removing Leaves
http://codeforces.com/contest/1385/problem/F
给一棵树,每次操作,选定一个节点,删除它相邻的 \(k\) 个叶子节点,直到不存在这样可以选的节点,问最多操作几次
维护一个 \(leaves_i\) 表示 \(i\) 相邻的有几个叶子节点,在用一个STL的 set
,\(set_i\) 存它相邻的不是叶子的节点
把这些 \(leaves\) 用优先队列维护起来,每次取出一个最大的,设为 \(u\)
只要 \(leaves_u\ge k\),就不断让 \(leaves_u,deg_u\) 减去 \(k\),答案加一,其中 \(deg\) 表示一个点的度数
如果这一通改完以后,如果 \(deg_u=1\),说明 \(u\) 变成了叶子节点,并且它唯一相邻的这个节点不是叶子节点(也就是 \(leaves_u=0\)),此时就要用到 \(set_u\) 了
用 \(set_u\) 找出它相邻的这个点 \(v\),因为 \(u\) 以前不是叶子节点,但现在是了,所以在 \(set_v\) 中抹去 \(u\),\(leaves_v\) 也要加一
如果 \(leaves_v\ge k\) 了,就把 \(v\) 加入队列(原来队列里的 \(v\) 此时就废弃了,所以每次取出的时候要判断一下)
这样做的原因也就是因为每次选取一个点进行操作后,可能会为这个点相邻的非叶子节点带来新的叶子,所以要反复加入回队列进行更新(比如说样例三)
但发现这样并不能很好的应对 \(k=1\) 的情况,所以特判一下,输出 \(n-1\) 就行了
#define N 200005
#define M 400005
struct graph{
int fir[N],nex[M],to[M],tot,t[M];
inline void add(int u,int v){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
}
}G;
struct data{
int id,leaves;
};
inline int operator < (const data &a,const data &b){
return a.leaves<b.leaves;
}
int n;
std::set<int>set[N];//存相邻的度数不为 1 的点
std::priority_queue<data>q;
int leaves[N],deg[N];
void dfs(int u,int fa){
if(deg[fa]==1) leaves[u]=1,set[u].erase(fa);
for(reg int v,i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
if(v==fa) continue;
if(deg[v]==1) set[u].erase(v),leaves[u]++;
dfs(v,u);
}
}
int main(){int T=read();while(T--){
n=read();int k=read();
for(reg int u,v,i=1;i<n;i++){
u=read();v=read();
G.add(u,v);G.add(v,u);
deg[v]++;deg[u]++;
set[u].insert(v);set[v].insert(u);
}
if(k==1){
printf(" %d\n",n-1);
for(reg int i=1;i<=n;i++) set[i].clear(),G.fir[i]=leaves[i]=deg[i]=0;
G.tot=0;
continue;
}
dfs(1,0);
for(reg int i=1;i<=n;i++) q.push((data){i,leaves[i]});
reg int u,v;int ans=0;
while(!q.empty()){
if(leaves[q.top().id]!=q.top().leaves){//判断一下 u 是否有效
q.pop();continue;
}
u=q.top().id;q.pop();
if(leaves[u]<k) break;
while(leaves[u]>=k) leaves[u]-=k,deg[u]-=k,ans++;
if(!leaves[u]&°[u]==1){
v=*set[u].begin();
leaves[v]++;
set[v].erase(u);
if(leaves[v]>=k) q.push((data){v,leaves[v]});
}
}
for(reg int i=1;i<=n;i++) set[i].clear(),G.fir[i]=leaves[i]=deg[i]=0;
while(!q.empty()) q.pop();G.tot=0;
printf(" %d\n",ans);
}
return 0;
}
CF1385G Columns Swaps
https://codeforces.com/problemset/problem/1385/G
给出两行数,每行 \(n\) 个,都小于等于 \(n\)
每次操作,交换同一列的两个数。问能不能通过若干操作使得两行数每一行都是长度为 \(n\) 的排列,如果能,求最小操作次数
首先如果有某个数出现次数不是 \(2\),显然不能
看了眼标签发现有 2-sat,不过就算不看也很容易想到 \(1\ldots n\) 表示不交换,\(n+1\ldots 2n\) 表示交换相应的列
然后写着写着发现这样建图出来的都是双向边,所以直接并查集就行了
考虑当前是第 \(i\) 行,第一行和第二行的数分别是 \(a_i,b_i\)
- 不交换
- 如果第一行有两个 \(a_i\),那么这一列不交换,另一个 \(a_i\) 所在的那一列必须交换(因为要保证每一行都只有一个 \(a_i\))
- 如果只有一个 \(a_i\),这一行不交换,第二行中的那个 \(a_i\) 所在的列也不交换
- 对于第二行的数(也就是 \(b_i\)),上面这两种情况也是同理
- 交换
- 如果第一行有两个 \(a_i\),这一列交换,则另一个必须不换
- 如果只有一个,这一行不换,那么第二行的那个 \(a_i\) 所在列也必须交换
- \(b_i\) 同理
根据上面的描述合并集合即可,具体实现上,用数组 _1[num][0/1],_2[num][0/1]
来表示第 \(1,2\) 行的第 \(1,2\) 个 \(num\) 在哪一列(如果没有就是 \(0\),所以也可以用这个数组来判断一行有几个 \(num\))
然后合并完以后,一个集合内的所有元素要么都成立,要么都不成立
所以,如果 \(i,i+n\) 在同一集合,则无解
然后对于每个集合,在合并的时候记录一个 \(val\),表示这个集合有多少个 \(>n\) 的元素(如果让这个集合成立,需要几次操作)
每次在 \(i,i+n\) 中贪心地选 \(val\) 小的让它成立,打上标记就行了
#define N 400005
int n;
int _1[N][2],_2[N][2];
int a[N],b[N];
int t[N];
int fa[N],val[N],vis[N],Ans[N];
int find(int k){return fa[k]==k?k:fa[k]=find(fa[k]);}
inline void link(int x,int y){
x=find(x);y=find(y);
if(x!=y) fa[x]=y,val[y]+=val[x];
}
int main(){int T=read();while(T--){
n=read();
for(reg int i=1;i<=n;i++)
vis[i]=vis[i+n]=t[i]=_1[i][0]=_1[i][1]=_2[i][0]=_2[i][1]=0,
fa[i]=i,fa[i+n]=i+n,val[i]=0,val[i+n]=1;
for(reg int x,i=1;i<=n;i++){
a[i]=x=read();t[x]++;
if(_1[x][0]) _1[x][1]=i;
else _1[x][0]=i;
}
for(reg int x,i=1;i<=n;i++){
b[i]=x=read();t[x]++;
if(_2[x][0]) _2[x][1]=i;
else _2[x][0]=i;
}
for(reg int i=1;i<=n;i++)if(t[i]!=2){
puts("-1"); goto FAIL;
}
for(reg int i=1;i<=n;i++){
if(a[i]==b[i]) continue;
//不换
if(_1[a[i]][1]) link(i,_1[a[i]][i==_1[a[i]][0]]+n);//另一个 a[i] 必须换
else link(i,_2[a[i]][0]);//另一个 a[i](在第二行)必须不换
if(_2[b[i]][1]) link(i,_2[b[i]][i==_2[b[i]][0]]+n);
else if(_2[b[i]][0]) link(i,_1[b[i]][0]);
//换
if(_1[a[i]][1]) link(i+n,_1[a[i]][i==_1[a[i]][0]]);//另一个必须不换
else link(i+n,_2[a[i]][0]+n);//这行只有一个 a[i],换走了,另一个(在第二行)必须换
if(_2[b[i]][1]) link(i+n,_2[b[i]][i==_2[b[i]][0]]);
else link(i+n,_1[b[i]][0]+n);
}
for(reg int i=1;i<=n;i++)if(find(i)==find(i+n)){
puts("-1");goto FAIL;
}
// puts("YES");
for(reg int i=1,fi,fin;i<=n;i++)if(!vis[fi=find(i)]&&!vis[fin=find(i+n)]){
if(val[fi]<=val[fin]) vis[fi]=1;
else vis[fin]=1;
}
Ans[0]=0;
for(reg int i=1;i<=n;i++)if(vis[find(i+n)]) Ans[++Ans[0]]=i;
printf("%d\n",Ans[0]);
for(reg int i=1;i<=Ans[0];i++) printf("%d ",Ans[i]);
EN;
FAIL:;
}
return 0;
}