C.Nastia and a Hidden Permutation
题目描述
有一个长度为 (n) 的未知排列,可以询问 ((t,i,j,x)),会返回如下值:
- (t=1:max(min(x,p_i),min(x+1,p_j)))
- (t=2:min(max(x,p_i),max(x+1,p_j)))
最多询问 (lfloorfrac{3cdot n}{2} floor+30) 次后确定这个排列
(3leq nleq 10^4)
解法
有一种思路是询问直接拿答案,如果用第一种操作拿答案,(p_i=1,x=n-1) 返回的就是 (p_j)
那么问题变成了找到 (p_i=1) 的 (i),要求在 (frac{n}{2}) 次之内找到。可以用第二种操作找。令 (x=1) 那么如果返回值是 (1) 就知道 (p_i=1),可以一对一对的找,如果返回值是 (2) 那么 (p_jleq 2),对 (p_j) 再问一次即可,否则 (p_j>2) 就不用问他了。
#include <cstdio>
const int M = 10005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,p,a[M];
int ask(int a,int b,int c,int d)
{
printf("? %d %d %d %d
",a,b,c,d);
fflush(stdout);
return read();
}
signed main()
{
T=read();
while(T--)
{
n=read();p=0;
for(int i=1;i<=n/2;i++)
{
int t1=ask(2,i,i+n/2,1);
if(t1==1) {p=i;break;}
else if(t1==2)
{
int t2=ask(2,i+n/2,i,1);
if(t2==1) {p=i+n/2;break;}
}
}
if(n%2 && !p) p=n;a[p]=1;
for(int i=1;i<=n;i++)
{
if(i!=p) a[i]=ask(1,p,i,n-1);
}
printf("!");
for(int i=1;i<=n;i++)
printf(" %d",a[i]);
puts("");
fflush(stdout);
}
}
D. Nastia Plays with a Tree
题目描述
给你一棵 (n) 个点的树,每次可以删掉一条树边任意加一条边(不要求联通),求最少操作数,要求输出方案。
(2leq nleq 10^5)
解法
这道题有一个迷惑点就是不要求联通,但是最优的方案是可以保证时时刻刻联通的。
其实可以树形 (dp),不考虑父边,子树内一定要构成一条链才行,但是考虑父边就有两种情况。定义纯链表示以子树根 (u) 为端点的链,定义中转链表示 (u) 为中转点的链,设 (dp[u][0/1]) 分别表示纯链(/)中转链的最小操作次数,转移:
- 纯链可以找到子树内的一条纯链继承,剩下的都选中转链接到这个纯链上,找 (dp[v][1]+1-dp[v][0]) 最大的即可。
- 中转链可以找到子树内的两条纯链继承,剩下的都选中转链接到这个纯链上,找 (dp[v][1]+1-dp[v][0]) 最大和次大的即可。
剩下的问题就是输出方案了,转移的时候记录一下父亲某个状态是否使用了儿子的纯链状态来转移,然后再搞一个 ( t dfs),设 (l[u]) 为链的左端点,(r[u]) 为链的右端点,讨论一下即可,时间复杂度 (O(n))
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,tot,f[M],dp[M][2],pd[M][2],l[M],r[M];
struct edge
{
int v,next;
}e[2*M];
void dfs(int u,int fa)
{
int mx1=0,mx2=0,p1=0,p2=0;
dp[u][0]=dp[u][1]=pd[u][0]=pd[u][1]=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);
dp[u][0]+=dp[v][1]+1;
dp[u][1]+=dp[v][1]+1;
int t=dp[v][1]+1-dp[v][0];
if(mx1<=t)
{
mx2=mx1;p2=p1;
mx1=t;p1=v;
}
else if(mx2<=t) mx2=t,p2=v;
}
dp[u][0]-=mx1;
dp[u][1]-=mx1+mx2;
pd[p1][0]=pd[p1][1]=pd[p2][1]=1;
}
void print(int u,int tp,int fa)
{
l[u]=r[u]=u;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
if(pd[v][tp])
{
print(v,0,u);
if(l[u]==u) l[u]=l[v];
else r[u]=l[v];//r[v]=v,所以右端点是l[v]
}
}
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
if(!pd[v][tp])
{
print(v,1,u);
printf("%d %d %d %d
",u,v,l[u],l[v]);
l[u]=r[v];
}
}
}
signed main()
{
T=read();
while(T--)
{
n=read();tot=0;
for(int i=1;i<=n;i++) f[i]=0;
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
dfs(1,0);
printf("%d
",min(dp[1][0],dp[1][1]));
if(dp[1][0]<=dp[1][1]) print(1,0,0);
else print(1,1,0);
}
}
E. Nastia and a Beautiful Matrix
题目描述
有 (k) 种颜色,第 (i) 种颜色数量是 (a_i),数量总和是 (m),求满足下列染色的最小正方形,:
- 对于每个 (2 imes2) 子矩形颜色数量不超过 (3)
- 对于每个 (2 imes 2) 子矩形的对角线颜色不同,未染色的不算在其中
(1leq m,kleq 10^5)
解法
( t oneindark) 告诉我们:这道题限制比较复杂,不好 ( t dp) 之类的,应该是构造了。然后这种相邻格子不同的限制可以往对矩阵染色方面想。
考虑 (n imes n) 的矩形是否满足条件,把原图划分成若干个 (2 imes 2) 的子矩形可以得到答案上界:(mleq n^2-lfloorfrac{n}{2} floor^2,max a_ileq ncdot lceilfrac{n}{2} ceil),那么考虑构造出这个答案上界,我们把矩形按下列方法染色,用一下官方的图:
我们让白色格子空出来,那么蓝色格子是必须填的,相邻的黄色格子和红色格子必须不同。可以贪心构造,设 (a_p=max a_i),那么我们先解决颜色 (p),我们优先把他填入红色格,如果红色格塞满了就填蓝色格,不难发现 (a_pleq) 红蓝格总数,也就是 (max a_ileq ncdot lceilfrac{n}{2} ceil)
其他的颜色就类似地填就行了,不难发现按这样填是一定不会冲突的,还有一个限制就是 (mleq)可填格子总数,也就是 (mleq n^2-lfloorfrac{n}{2} floor^2)
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int M = 100005;
#define pii pair<int,int>
#define make make_pair
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,ans[1005][1005];pii a[M];
void work()
{
n=read();m=read();
for(int i=1;i<=m;i++)
a[i].first=read(),a[i].second=i;
sort(a+1,a+1+m);
for(int s=1;;s++)
{
if(n>s*s-(s/2)*(s/2)) continue;
if(a[m].first>s*((s+1)/2)) continue;
vector<pii> x,y,z;
for(int i=1;i<=s;i++)
for(int j=1;j<=s;j++)
ans[i][j]=0;
for(int i=1;i<=s;i++)
for(int j=1;j<=s;j++)
{
if((i+j)%2)
{
if(i%2) x.push_back(make(i,j));
else y.push_back(make(i,j));
}
else if(i%2)
z.push_back(make(i,j));
}
for(int i=m;i>=1;i--)
{
int p=a[i].first,q=a[i].second;
vector<pii> &cur=(x.empty())?y:x;
while(!cur.empty() && p)
{
pii t=cur.back();cur.pop_back();
ans[t.first][t.second]=q;p--;
}
while(p--)
{
pii t=z.back();z.pop_back();
ans[t.first][t.second]=q;
}
}
printf("%d
",s);
for(int i=1;i<=s;i++,puts(""))
for(int j=1;j<=s;j++)
printf("%d ",ans[i][j]);
return ;
}
}
signed main()
{
T=read();
while(T--) {work();}
}