题目质量都好高啊...
A:求一个是$X$的倍数但不是$Y$的倍数的数,无解输出$-1$
无解就是$Y|X$,否则输出$X$即可
B:给定$a_{1\cdots n},b_{1\cdots n}$,求是否能通过($a_i+=2,b_j+=1$)这样的操作使$a,b$相等
如果$\sum a_i\geq\sum b_i$那么无解,否则操作次数为$\sum\limits b_i-\sum a_i$
因为操作可以异步进行,所以先把$+2$用完看能否使得使得所有$a_i\geq b_i$,这一步至少要操作$\sum[a_i\lt b_i]\left\lceil\frac{b_i-a_i}2\right\rceil$次,如果这里都$\gt\sum b_i-\sum a_i$那就无解了,否则剩下的$+2$随便用,最后用$+1$即可构造合法解
#include<stdio.h>
typedef long long ll;
int a[10010],b[10010];
int main(){
int n,i;
ll s;
scanf("%d",&n);
s=0;
for(i=1;i<=n;i++){
scanf("%d",a+i);
s-=a[i];
}
for(i=1;i<=n;i++){
scanf("%d",b+i);
s+=b[i];
}
for(i=1;i<=n;i++){
if(a[i]<b[i])s-=(b[i]-a[i]+1)/2;
}
puts(s<0?"No":"Yes");
}
C:交互题,有$n(2\nmid n,n\geq3)$个座位排成一圈,每个座位要么是空的,要么被男或女占据,没有一对相邻座位坐了同性别的人,至少有一个座位是空的,你可以询问一个位置坐了什么人,要找出任意一个空的座位
如果长度为奇数的区间的两个端点性别不同,那么这个区间内肯定有空座位,直接二分即可,注意要时刻保证二分的区间长度为奇数
总询问次数为$O(\log n)$
#include<stdio.h>
#include<string.h>
int c[100010];
char s[10];
int getn(){
int n;
scanf("%d",&n);
return n;
}
int get(int x){
if(~c[x])return c[x];
printf("%d\n",x);
fflush(stdout);
scanf("%s",s);
if(s[0]=='V')throw 0;
return c[x]=s[0]=='M';
}
int main(){
int n,l,r,mid;
memset(c,-1,sizeof(c));
n=getn();
try{
l=0;
r=n-1;
while(r-l>2){
mid=(l+r)>>1;
if((mid-l+1)&1){
if(get(l)^get(mid))
r=mid;
else
l=mid;
}else{
if(get(mid+1)^get(r))
l=mid+1;
else
r=mid+1;
}
}
while(l<=r)get(l++);
}catch(int e){}
}
D:给一个带点权的森林,你可以用$v_i+v_j$的代价连接$i,j$两点,问使得整个图连通的最小代价,一个点被选后不能再被选
最优策略是把它连成一棵树,至少要连$n-m-1$条边,要选$2(n-m-1)$个点,如果$2(n-m-1)\gt n$那么无解
现在问题变为:要选$2(n-m-1)$个点,且每个连通块必须有至少一个点被选,如果按这个要求选出来了,那么我们可以构造一组对应的解(每次选包含最多未选节点的两个连通块,把它们连起来)
所以直接贪心,在每个连通块选一个权值最小的点,剩下的点拿出来排序即可
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
int h[100010],nex[200010],to[200010],M;
void add(int a,int b){
M++;
to[M]=b;
nex[M]=h[a];
h[a]=M;
}
bool vis[100010];
int v[100010],p[100010],pa[100010],ma;
void dfs(int x){
if(vis[x])return;
vis[x]=1;
p[++M]=v[x];
for(int i=h[x];i;i=nex[i])dfs(to[i]);
}
int main(){
int n,m,i,j,x,y,cnt;
ll ans;
scanf("%d%d",&n,&m);
if(2*(n-m-1)>n){
puts("Impossible");
return 0;
}
ans=0;
cnt=2*(n-m-1);
for(i=1;i<=n;i++)scanf("%d",v+i);
while(m--){
scanf("%d%d",&x,&y);
x++;
y++;
add(x,y);
add(y,x);
}
for(i=1;i<=n&&cnt;i++){
if(!vis[i]){
M=0;
dfs(i);
sort(p+1,p+M+1);
ans+=p[1];
for(j=2;j<=M;j++)pa[++ma]=p[j];
cnt--;
}
}
sort(pa+1,pa+ma+1);
for(i=1;i<=cnt;i++)ans+=pa[i];
printf("%lld",ans);
}
E:给定一棵边权为$1$的树,求最小的$k$,使得存在一种安排$x_{1\cdots k}$的方式满足$(\text{dis}(x_1,u),\cdots,\text{dis}(x_k,u))(1\leq u\leq n)$这$n$个$k$维向量互不相同
如果整棵树是一条链,那么答案是$1$:把$x_1$放在链的一端即可
对于一个节点$v$,如果删除$v$后可以获得$k$个子树,且这$k$个子树中有$\geq2$个子树不含被安排的节点,那么向着这些子树走一步走到的那些节点所对应的向量是一样的,所以这$k$个子树中至少有$k-1$个子树含有被安排的节点
如果找一个度数$\geq3$的节点作为根,并且把限制写成:对任意有$k$个子树的节点,它必须有至少$k-1$个子树包含被安排的节点,容易发现这和原限制是一样的:对根的限制没有变化,对非根节点,它在父亲的方向一定有被安排的节点(因为根的度数$\geq3$)
所以考虑DP,设$f_x$表示在$x$的子树中,每个节点都满足以上限制的最小安排节点数,转移就先把儿子的DP值加起来,如果有$c(c\gt1)$个儿子的DP值为$0$,那么为了让$x$满足限制,相应地要再安排$c-1$个节点
#include<stdio.h>
int h[100010],nex[200010],to[200010],M;
void add(int a,int b){
M++;
to[M]=b;
nex[M]=h[a];
h[a]=M;
}
int d[100010],f[100010];
void dfs(int fa,int x){
int i,c;
c=0;
for(i=h[x];i;i=nex[i]){
if(to[i]!=fa){
dfs(x,to[i]);
f[x]+=f[to[i]];
c+=!f[to[i]];
}
}
if(c>1)f[x]+=c-1;
}
int main(){
int n,i,x,y;
scanf("%d",&n);
for(i=1;i<n;i++){
scanf("%d%d",&x,&y);
x++;
y++;
add(x,y);
add(y,x);
d[x]++;
d[y]++;
}
for(i=1;i<=n;i++){
if(d[i]>2)break;
}
if(i>n){
putchar('1');
return 0;
}
dfs(0,i);
printf("%d",f[i]);
}
F:给定一棵带边权$(0\leq v\leq 15)$的树,每次你可以选择一条简单路径和$x$,并且将这条路径上的边都$\text{xor }x$,问把所有边变成$0$的最小操作次数
神仙转化:令每个点的点权为与它相邻的边的权值异或和,那么路径操作就变成了选两个点,将其$\text{xor }x$
对一个操作序列,建一个图:如果一次操作选了$i,j$,那么连边$(i,j)$,在最优策略下建出来的的图肯定是一个森林,因为对于一个最终图中的连通块,这个连通块的异或和始终不变(最后是$0$,所以开始时也是$0$),所以问题转化为:把这$n$个数划分为尽可能多的集合,使得每个集合的异或和为$0$
对于$0$,肯定让每个$0$单独成集合,对于非$0$数,贪心地两两配对,这样最后只会剩下最多$15$个互不相同的数,用$O(3^v)$的枚举子集DP即可
#include<stdio.h>
#include<algorithm>
using namespace std;
int lowbit(int x){return x&-x;}
int a[100010],c[20],v[32768],s[32768],f[32768];
int main(){
int n,i,j,x,y,z,res,M;
scanf("%d",&n);
for(i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
a[x+1]^=z;
a[y+1]^=z;
}
for(i=1;i<=n;i++)c[a[i]]++;
res=M=0;
res+=c[0];
for(i=1;i<16;i++){
res+=c[i]/2;
if(c[i]&1)v[1<<(M++)]=i;
}
for(i=1;i<1<<M;i++)s[i]=s[i^lowbit(i)]^v[lowbit(i)];
for(i=1;i<1<<M;i++){
if(s[i]==0){
for(j=i;j;j=(j-1)&i){
if(s[j]==0)f[i]=max(f[i],f[i^j]+1);
}
}
}
printf("%d",n-(res+f[(1<<M)-1]));
}
以下的题开始鬼畜了==
G:在数轴上有$n$对传送门将数轴划分为$2n+1$个区间,一个人从左边开始一直向右走,每碰到一个传送门就立刻被传送到与之配对的传送门的右边,最后走到最右边,现在给出中间的$2n-1$个区间的被经过情况(经过为$1$,不经过为$0$),问是否可能,如果可能,构造一个传送门的安排方案,看图可以帮助理解题意
先把整个数轴左右相连连成一个环,这样就有$n$对传送门和$2n$个区间,对一个确定的传送门安排方案,每段区间的前驱和后继都是确定的,所有区间形成了若干个环,从这里容易看出在原问题中,从左边开始走是一定可以走到右边的
1.如果所有区间都要被经过
i.如果$n$是偶数,那么直接构造:$1,2,1,2,\cdots,n-1,n,n-1,n$
ii.如果$n$是奇数,那么无解,可以归纳证明:当$n=1$时有$2$个环,无解;$n$每增加$1$就会合并两个环或增加一个环,即环的奇偶性变化,所以当$n$为奇数时总有偶数个环,也就是无解
2.有一些区间不被经过
我们把所有的极长连续$1$找出来,设$c$为$11$的个数,对$c$分类讨论
i.$2\nmid c$:无解,因为$11$夹着的传送门必须两两配对
ii.$4|c$:有解,把所有极长连续$1$连起来,情况变为所有区间都要被经过,而且区间数量是$4$的倍数,转化为1.i.
iii.$4\nmid c$且只有一段含有$11$:无解,因为这种情况下所有的区间必须被合并,此时区间个数$\equiv2(\bmod4)$,转化为1.ii.
iv.$4\nmid c$且有$\geq2$段含有$11$:有解,我们可以用一对传送门让$c$减少$2$,转化为1.i.
#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
char s[200010];
int a[200010],st[200010],ed[200010],ans[200010],M,lb;
vector<int>in[200010];
int p[200010],N;
int main(){
int n,i,j,sum,tmp;
bool flag;
scanf("%d%s",&n,s+1);
a[0]=1;
for(i=1;s[i];i++)a[i]=s[i]-'0';
n<<=1;
for(i=0;i<n;i++){
if(!a[i]&&a[(i+1)%n]){
M++;
st[M]=i;
for(j=(i+1)%n;a[j];j=(j+1)%n){
if(a[(j+1)%n])in[M].push_back(j);
}
ed[M]=(j-1+n)%n;
}
}
#define wa {puts("No");return 0;}
if(!M){
n>>=1;
if(n&1)wa
puts("Yes");
for(i=1;i<=n;i+=2)printf("%d %d %d %d ",i,i+1,i,i+1);
return 0;
}
sum=0;
for(i=1;i<=M;i++)sum+=in[i].size();
if(sum&1)wa
if(sum&2){
tmp=0;
for(i=1;i<=M;i++)tmp+=!in[i].empty();
if(tmp==1)wa
for(i=1;in[i].empty();i++);
for(j=i+1;in[j].empty();j++);
swap(ed[i],ed[j]);
ans[*in[i].rbegin()]=ans[*in[j].rbegin()]=++lb;
in[i].pop_back();
in[j].pop_back();
}
for(i=1;i<M;i++)ans[ed[i]]=ans[st[i+1]]=++lb;
ans[ed[M]]=ans[st[1]]=++lb;
for(i=1;i<=M;i++){
for(int x:in[i])p[++N]=x;
}
for(i=1;i<=N;i+=4){
ans[p[i]]=ans[p[i+2]]=++lb;
ans[p[i+1]]=ans[p[i+3]]=++lb;
}
flag=0;
for(i=0;i<n;i++){
if(!ans[i]){
flag=!flag;
if(flag)lb++;
ans[i]=lb;
}
}
puts("Yes");
for(i=0;i<n;i++)printf("%d ",ans[i]);
}
H:给一棵带点权的树,点权$a_{1\cdots n}$是一个排列,每次可以选择一个节点$x$并将其到根路径上的点权向上shift(根的点权会到$x$),要用$O(n\log n)$次操作使得$a_i=i$
先考虑一条链的情况,我们维护从链底开始的连续一段,使得这里面的节点相对有序(指按最终顺序),每次像插入排序一样把根的权值插到合适位置
对于一棵树,整个做法基于以下事实:对每个叶子,找出它向上的极长链使得链上除了这个叶子的每个点都只有一个儿子,每次把这些链删除,这样的过程只会有$O(\log n)$次
用DP辅助分析,设$f_x$表示用这种方法需要多少次操作把子树$x$删完,设$mx$是$x$的儿子中最大的$f$,如果有$\geq2$个最大值,那么$f_x=mx+1$,否则$f_x=mx$,基于这个DP,我们可以归纳证得:若$f_x=k$,那么$siz_x\geq2^{k-1}$
所以,如果我们能用$O(n)$次操作让这些链满足$a_i=i$,那么总操作次数就是$O(n\log n)$
考虑当前根的权值,如果根的权值要到某一条链中,那么像链的做法一样把它插到对应位置,否则把它插到最深的未排序位置,并将其标记为不能再选取
在整个过程中,每次要么使得某一条链增加排好序的长度,要么把一个权值标记为不能再选,所以总操作次数为$n+$链的长度和,即$O(n)$
总时间复杂度$O(n^2\log n)$
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
int ans[25010],op;
int fa[2010],dep[2010],a[2010],ch[2010],dn[2010],up[2010],rk[2010],so[2010],bl[2010],M;
bool del[2010],us[2010];
void rot(int x){
ans[++op]=x;
int v=a[x];
a[x]=a[1];
for(x=fa[x];x;x=fa[x])swap(v,a[x]);
}
int main(){
int n,i,j,cnt;
bool flag;
scanf("%d",&n);
dep[1]=1;
for(i=2;i<=n;i++){
scanf("%d",fa+i);
fa[i]++;
ch[fa[i]]++;
dep[i]=dep[fa[i]]+1;
}
for(i=1;i<=n;i++){
scanf("%d",a+i);
a[i]++;
}
while(1){
flag=1;
for(i=1;i<=n;i++){
if(a[i]!=i){
flag=0;
break;
}
}
if(flag)break;
M=0;
for(i=1;i<=n;i++){
if(!del[i]&&ch[i]==0){
M++;
dn[M]=i;
for(j=i;fa[j]&&ch[fa[j]]==1;j=fa[j]);
up[M]=j;
so[M]=0;
cnt=0;
for(j=i;dep[j]>=dep[up[M]];j=fa[j]){
rk[j]=++cnt;
bl[j]=M;
}
}
}
memset(us,0,sizeof(us));
while(1){
flag=1;
for(i=1;i<=M;i++){
for(j=dn[i];dep[j]>=dep[up[i]];j=fa[j]){
if(a[j]!=j){
flag=0;
break;
}
}
if(!flag)break;
}
if(flag)break;
us[a[1]]=1;
if(bl[a[1]]){//insert it to a chain
i=bl[a[1]];
if(!so[i]){
rot(dn[i]);
so[i]=dn[i];
}else{
for(j=dn[i];dep[j]>=dep[so[i]]&&rk[a[j]]<rk[a[1]];j=fa[j]);
rot(j);
so[i]=fa[so[i]];
}
}else{
j=0;
for(i=1;i<=n;i++){
if(!del[i]&&!us[a[i]]&&dep[i]>dep[j])j=i;
}
rot(j);
}
}
for(i=1;i<=M;i++){
for(j=dn[i];dep[j]>=dep[up[i]];j=fa[j])del[j]=1;
ch[fa[up[i]]]--;
}
}
printf("%d\n",op);
for(i=1;i<=op;i++)printf("%d\n",ans[i]-1);
}
I:一个$H\times W$的网格中有$n(n\leq30)$个黑格子,求所有白格子之间的最短路径和(不能经过黑格子)
如果第$i,i+1$行都没有黑格,那么$i,i+1$行中间这条线将网格分为两个区域,两个区域内的最短路不会跨过这条线,区域间的最短路一定会跨过这条线,跨过这条线的贡献为$iw\cdot(n-i)w$,之后我们可以将这两行缩成一行,并打上标记“这行的每个格子都代表着原网格中的$2$个格子”
对列也这样操作,于是整个网格的边长被缩成了$O(n)$级别,这时直接$O(n^4)$统计即可
#include<stdio.h>
#include<string.h>
typedef long long ll;
const int mod=1000000007;
const int go[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int mul(int a,int b){return(ll)a*b%mod;}
int x[40],y[40],sx[1000010],sy[1000010],ix[1000010],iy[1000010],mx,my;
bool bx[1000010],by[1000010],mp[70][70];
int dis[70][70],vx[70],vy[70];
struct pr{
int x,y;
pr(int x=0,int y=0):x(x),y(y){}
}q[4010];
bool ok(int x,int y){
return 0<x&&x<=mx&&0<y&&y<=my&&!mp[x][y];
}
void bfs(pr t){
int head,tail,x,y,i;
memset(dis,63,sizeof(dis));
head=tail=1;
q[1]=t;
dis[t.x][t.y]=0;
while(head<=tail){
t=q[head++];
for(i=0;i<4;i++){
x=t.x+go[i][0];
y=t.y+go[i][1];
if(ok(x,y)&&dis[t.x][t.y]+1<dis[x][y]){
dis[x][y]=dis[t.x][t.y]+1;
q[++tail]=pr(x,y);
}
}
}
}
int main(){
int h,w,n,i,j,k,l,res,tmp;
scanf("%d%d%d",&h,&w,&n);
for(i=1;i<=n;i++){
scanf("%d%d",x+i,y+i);
bx[++x[i]]=1;
by[++y[i]]=1;
sx[x[i]]++;
sy[y[i]]++;
}
res=0;
for(i=1;i<=h;i++)sx[i]+=sx[i-1];
for(i=1;i<=w;i++)sy[i]+=sy[i-1];
ix[1]=++mx;
for(i=2;i<=h;i++){
if(!bx[i]&&!bx[i-1]){
ix[i]=ix[i-1];
(res+=mul(mul(i-1,w)-sx[i-1],mul(h-i+1,w)-(sx[h]-sx[i-1])))%=mod;
}else
ix[i]=++mx;
}
iy[1]=++my;
for(i=2;i<=w;i++){
if(!by[i]&&!by[i-1]){
iy[i]=iy[i-1];
(res+=mul(mul(i-1,h)-sy[i-1],mul(w-i+1,h)-(sy[w]-sy[i-1])))%=mod;
}else
iy[i]=++my;
}
for(i=1;i<=h;i=j){
for(j=i;ix[j]==ix[i];j++);
vx[ix[i]]=j-i;
}
for(i=1;i<=w;i=j){
for(j=i;iy[j]==iy[i];j++);
vy[iy[i]]=j-i;
}
for(i=1;i<=n;i++)mp[ix[x[i]]][iy[y[i]]]=1;
for(i=1;i<=mx;i++){
for(j=1;j<=my;j++){
if(!mp[i][j]){
bfs(pr(i,j));
tmp=0;
for(k=i;k<=mx;k++){
for(l=1;l<=my;l++){
if((k>i||l>j)&&!mp[k][l])(tmp+=mul(dis[k][l],mul(vx[k],vy[l])))%=mod;
}
}
(res+=mul(tmp,mul(vx[i],vy[j])))%=mod;
}
}
}
printf("%d",(res+mod)%mod);
}
J:太毒了...咕掉算了...