IOI 2021 集训队作业瞎做 Part 2

UH [todo]

在这里放个 [todo] 是来证明我学傻了的。

首先显然可以转化成二分图最小点覆盖集(前缀和后缀连边)。

(可能这玩意并不叫最小覆盖点集,意会就好

然后根据某定理,这个大小等于最大匹配大小。

至于构造方案,对于一条匹配边,两点肯定至少选一个(否则这条边就没被覆盖),而且只能选一个(否则选择点数就大于最大匹配)。而对于不在匹配中的点,不会被选(同理)。

那么对于每条未匹配边都变成了一个 2-SAT 的限制。

时间复杂度就那样。

这显然不是最优解法,等以后脑子清醒了再来填坑。

upd:草。

对于每条未匹配边,两点至少一个是匹配点。若只有属于左半部分的那个是匹配点,显然应该选那个匹配点。

选了那个点之后,与那个点匹配的点(是个右半部分的)也就不能用了,可能会导致一些新的未匹配边只能选左边。

不过这是一个 DFS 能搞定的事。

最后剩下一些没考虑的匹配边和未匹配边。此时每条未匹配边的右半部分都是可用的匹配点,所以剩下的所有匹配边都取右边点就行了。

SD

即等价于选一堆 \(2^x3^y\),要求 \(x_1\ge x_2\) 时必须要 \(y_1<y_2\)

对于 \(n=1\) 显然。

对于 \(n\) 是偶数,可以递归到 \(n/2\),并将 \(n/2\) 的拆分全部乘 \(2\)。显然这里面的所有数会比前面所有数的 \(2\) 的次数大。

对于 \(n\) 是奇数,可以递归到 \((n-3^k)/2\),其中 \(3^k\le n\)\(k\) 最大。注意到 \((n-3^k)/2<3^k\),所以这个数会比后面所有数的 \(3\) 的次数要大。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
ll n,tmp[maxn];
int tl;
void work(ll n,ll pr){
	if(!n) return;
	if(n<=3) return tmp[++tl]=pr*n,void();
	if(n%2==0) return work(n/2,pr*2);
	else{
		ll t=1;
		while(t<=n) t*=3;
		t/=3;
		return tmp[++tl]=pr*t,work(n-t,pr);
	}
}
void solve(){
	n=read();
	tl=0;
	work(n,1);
	printf("%d\n",tl);
	FOR(i,1,tl) printf("%lld ",tmp[i]);
	puts("");
}
int main(){
	freopen("distribution.in","r",stdin);
	freopen("distribution.out","w",stdout);
	int T=read();
	while(T--) solve();
}

NJ [todo]

度数不超过 \(3\),显然两点间最大流也不超过 \(3\)

两点最大流 \(>0\),当且仅当一开始联通。这个对数随便算。

两点最大流 \(>1\),当且仅当一开始在同一个双连通分量里。这个对数随便算。

两点最大流 \(>2\),当且仅当任意枚举一条边断掉,这两点都在同一双连通分量里。这个的算法,可以枚举断边,然后将每个点依次所在的双连通分量编号序列哈希(可能有其它判相等的方法,问题不大)。

有了这三个数,答案也随便算。

时间复杂度 \(O((n+m)^2)\)

很没意思,代码先咕着。

ID

考虑分治。

一开始是个大图形,按照两点在多边形边上距离最远的边(下称直径)分开成两个小图形。这样两边的点数不会差太多,递归层数也就不会太多。

对于两点在一边的询问,递归下去求解。否则,两点的路径一定经过直径两端点中的至少一个。以这两个点为起点分别 BFS。

时间复杂度 \(O(n\log n)\)

(好像还要稍微证一下每次两边不会差的太远?)

随便选择一个点 \(i\),考虑其所有出边指向的点(包括多边形边),并在两个半周上各选择距离最远的点 \(j,k\)

\(j,k\) 之间没有连边,则 \(i,j,\dots,k\) 这个多边形没法被剖分(\(\dots\) 表示 \(j,k\) 之间的点)。所以 \(j,k\) 有连边。

\(i\) 在劣弧 \(jk\)(感性理解下劣弧的意思),那么忽略劣弧 \(jk\) 间的所有点,接着考虑(注意后面的过程中距离仍然是原多边形,即这些被忽略的点也计入距离,只是不再作为 \(i,j,k\) 出现)。

否则,\(ij,jk,ki\) 的距离之和就是点数,最长的一条肯定不小于 \(\frac{1}{3}\) 倍点数。

注意,虽然忽略了一些点,每次仍然是可以找到 \(j,k\) 的,因为每次只删劣弧(且劣弧端点没有被删),所以与其相邻的两点分别在两个半周上。

所以递归层数不超过 \(\log_{1.5}n\)

我目前只会构造一层使得分成一边 \(\frac{1}{3}\) 一边 \(\frac{2}{3}\),但是到下一层好像就会分得飞快。反正就是不会卡满(

(有更紧的上界欢迎来 D /kel)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef pair<PII,int> PIII;
const int maxn=222222,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,m,ans[maxn],id[maxn],el,head[maxn],to[maxn],nxt[maxn],dis[2][maxn],que[maxn],h,r,at[maxn];
vector<int> v;
vector<PII> e;
vector<PIII> q;
inline void add(int u,int v){
	to[++el]=v;nxt[el]=head[u];head[u]=el;
	to[++el]=u;nxt[el]=head[v];head[v]=el;
}
void build(vector<int> &v,vector<PII> &e){
	FOR(i,0,(int)v.size()-2) add(v[i],v[i+1]);
	add(v.back(),v.front());
	FOR(i,0,(int)e.size()-1) add(e[i].first,e[i].second);
}
void clear(vector<int> &v){
	FOR(i,1,el) to[i]=nxt[i]=0;
	FOR(i,0,(int)v.size()-1) head[v[i]]=0;
	el=0; 
}
void bfs(int s,vector<int> &v,int *d){
	FOR(i,0,(int)v.size()-1) d[v[i]]=-1;
	d[s]=0;
	que[h=r=1]=s;
	while(h<=r){
		int u=que[h++];
		for(int i=head[u];i;i=nxt[i]){
			int v=to[i];
			if(d[v]==-1) d[v]=d[u]+1,que[++r]=v;
		} 
	}
}
void solve(vector<int> &v,vector<PII> &e,vector<PIII> &q){
	if((int)v.size()<=3){
		FOR(i,0,(int)q.size()-1) ans[q[i].second]=q[i].first.first!=q[i].first.second;
		return;
	}
	vector<int> v1,v2;
	vector<PII> e1,e2;
	vector<PIII> q1,q2;
	FOR(i,0,(int)v.size()-1) id[v[i]]=i;
	int U=0,V=0,D=0;
	FOR(i,0,(int)e.size()-1){
		int uu=e[i].first,vv=e[i].second,d=abs(id[uu]-id[vv]);
		d=min(d,(int)v.size()-d);
		if(d>D) U=uu,V=vv,D=d;
	}
	build(v,e);
	bfs(U,v,dis[0]);
	bfs(V,v,dis[1]);
	clear(v);
	int cur=1;
	FOR(i,0,(int)v.size()-1){
		at[v[i]]=0;
		if(v[i]==U || v[i]==V){
			cur=3-cur;
			v1.PB(v[i]);
			v2.PB(v[i]);
		}
		else{
			at[v[i]]=cur;
			if(cur==1) v1.PB(v[i]);
			else v2.PB(v[i]);
		}
	}
	FOR(i,0,(int)e.size()-1){
		int u=e[i].first,v=e[i].second;
		assert(!at[u] || !at[v] || at[u]==at[v]);
		if(at[u]==1 || at[v]==1) e1.PB(e[i]);
		else if(at[u]==2 || at[v]==2) e2.PB(e[i]);
	}
	FOR(i,0,(int)q.size()-1){
		int u=q[i].first.first,v=q[i].first.second;
		if(at[u]!=at[v]) ans[q[i].second]=min(dis[0][u]+dis[0][v],dis[1][u]+dis[1][v]);
		else if(!at[u]) ans[q[i].second]=u!=v;
		else if(at[u]==1) q1.PB(q[i]);
		else q2.PB(q[i]);
	}
	solve(v1,e1,q1);
	solve(v2,e2,q2);
}
int main(){
	freopen("distance.in","r",stdin);
	freopen("distance.out","w",stdout); 
	n=read();
	FOR(i,1,n-3){
		int u=read(),v=read();
		e.PB(MP(u,v));
	}
	m=read();
	FOR(i,1,m){
		int u=read(),v=read();
		q.PB(MP(MP(u,v),i)); 
	}
	FOR(i,1,n) v.PB(i);
	solve(v,e,q);
	FOR(i,1,m) printf("%d\n",ans[i]);
}

KA [todo]

好不可做啊,感觉除了搜没啥做法吧???

题面太难看了,所以写个 [todo] 以免以后再看自闭。

IL

普及难度。

枚举最高峰的位置 \(i\),二分高度 \(mid\),然后判断给出的石头数是否足够。

注意到最高峰左边要加到 \(mid-1\),再左边要加到 \(mid-2\),直到一个本来就足够的地方为止,再左边就不用操作。

设这个位置是 \(j\),那么有 \(a_j+i-j\ge mid\),即 \(a_j-j\ge mid-i\)。可以二分。

右边同理,然后加的石头数就很好算了。

时间复杂度 \(O(n\log n\log a)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,a[maxn],st1[18][maxn],st2[18][maxn],ans,p[maxn],s[maxn];
ll k,pre[maxn];
inline ll sum(int l,int r){return 1ll*(l+r)*(r-l+1)/2;}
int main(){
//	freopen("landscape.in","r",stdin);
//	freopen("landscape.out","w",stdout); 
	n=read();k=read();
	FOR(i,1,n) a[i]=read(),pre[i]=pre[i-1]+a[i];
	p[0]=s[n+1]=-1e9; 
	FOR(i,1,n){
		st1[0][i]=a[i]-i;
		st2[0][i]=a[i]+i;
		p[i]=max(p[i-1],a[i]-i);
	}
	ROF(i,n,1) s[i]=max(s[i+1],a[i]+i);
	FOR(i,1,17) FOR(j,1,n-(1<<i)+1){
		st1[i][j]=max(st1[i-1][j],st1[i-1][j+(1<<(i-1))]);
		st2[i][j]=max(st2[i-1][j],st2[i-1][j+(1<<(i-1))]);
	}
	FOR(i,1,n){
		int l=a[i],r=min(p[i]+i,s[i]-i);
		while(l<r){
			int mid=(l+r+1)>>1;
			int lft=i,rig=i;
			ROF(j,17,0) if(lft>(1<<j) && st1[j][lft-(1<<j)]<mid-i) lft-=1<<j;
			ROF(j,17,0) if(rig+(1<<j)<=n && st2[j][rig+1]<mid+i) rig+=1<<j;
			lft=min(lft+1,i);
			rig=max(rig-1,i);
			if(sum(mid-i+lft,mid)+sum(mid+i-rig,mid)-mid-(pre[rig]-pre[lft-1])<=k) l=mid;
			else r=mid-1;
		}
		ans=max(ans,l);
	}
	printf("%d\n",ans);
}

LL [todo]

我现在就去杀了出题人全家,别拦我。

首先,你需要看到坐标只有 \(1000\) 这个范围。(官方题解把这一步叫 key observation,看起来是真的。)

然后跟我一起问候出题人吧。

GL

好无脑的一题。然后我居然还想了好久。

把所有路径按长度从大到小排序,一个一个加入。

一开始把所有点颜色都看成一样的一种。每次加入一个路径,先判断原来这条路径是否同色,然后染成新的颜色。

直接树剖可以 \(O(n\log^2 n)\)

虽然很没意思但还是写了。

好像有更牛逼的做法?以 后 再 说。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=444444,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
struct hhh{
	int u,v,w;
	bool operator<(const hhh &h)const{return w>h.w;}
}h[maxn];
int n,m,el,head[maxn],to[maxn],nxt[maxn],fa[maxn],dep[maxn],sz[maxn],dfn[maxn],cnt,son[maxn],top[maxn],col[maxn],cov[maxn],cc;
char op[10];
inline void add(int u,int v){
	to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs1(int u,int f){
	dep[u]=dep[fa[u]=f]+1;
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(v==f) continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[v]>sz[son[u]]) son[u]=v;
	}
	sz[u]++;
}
void dfs2(int u,int topf){
	top[u]=topf;
	dfn[u]=++cnt;
	if(son[u]) dfs2(son[u],topf);
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(v==son[u] || v==fa[u]) continue;
		dfs2(v,v);
	}
}
int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
inline void pushup(int o){
	col[o]=col[o<<1]==col[o<<1|1]?col[o<<1]:-1;
}
inline void setcov(int o,int c){
	col[o]=cov[o]=c;
}
inline void pushdown(int o){
	if(col[o]!=-1){
		setcov(o<<1,col[o]);
		setcov(o<<1|1,col[o]);
		col[o]=-1;
	}
}
void build(int o,int l,int r){
	if(l==r) return col[o]=0,cov[o]=-1,void();
	int mid=(l+r)>>1;
	build(lson);build(rson);
	pushup(o);
}
void update(int o,int l,int r,int ql,int qr,int c){
	if(l>=ql && r<=qr) return setcov(o,c);
	pushdown(o);
	int mid=(l+r)>>1;
	if(mid>=ql) update(lson,ql,qr,c);
	if(mid<qr) update(rson,ql,qr,c);
	pushup(o);
}
int query(int o,int l,int r,int ql,int qr){
	if(l>=ql && r<=qr) return col[o];
	pushdown(o);
	int mid=(l+r)>>1;
	if(mid<ql) return query(rson,ql,qr);
	if(mid>=qr) return query(lson,ql,qr);
	int x=query(lson,ql,qr),y=query(rson,ql,qr);
	return x==y?x:-1; 
}
void update(int u,int v,int c){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,1,n,dfn[top[u]],dfn[u],c);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	update(1,1,n,dfn[u],dfn[v],c);
}
int query(int u,int v){
	int col=-2;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		int x=query(1,1,n,dfn[top[u]],dfn[u]);
		if(col==-2) col=x;
		else col=col==x?col:-1;
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	int x=query(1,1,n,dfn[u],dfn[v]);
	if(col==-2) col=x; 
	return col==x?col:-1;
}
int main(){
	n=read();m=read();
	FOR(i,1,n-1){
		int u=read(),v=read();
		add(u,v);add(v,u);
	}
	dfs1(1,0);dfs2(1,1);
	FOR(i,1,m){
		int u=read(),v=read(),w=dep[u]+dep[v]-2*dep[lca(u,v)];
		h[i]=(hhh){u,v,w};
	} 
	sort(h+1,h+m+1);
	build(1,1,n);
	FOR(i,1,m){
		int u=h[i].u,v=h[i].v;
		if(query(u,v)==-1) return puts("No"),0;
		update(u,v,++cc);
	}
	puts("Yes");
}

OJ

对于每条边,求出当询问的 \(r\) 是正无穷,\(l\) 最小是多少时,这条边会在最小生成森林上(设这个为 \(x_i\))。

这个可以排序后用 LCT 维护最大生成森林。

然后询问就变成了,对于边权在 \([l,r]\) 的边,把 \(x_i\le l\) 的边权求和。

可持久化线段树板子。

时间复杂度反正一个 log。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=111111,maxm=1111111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
struct edge{
	int u,v,w;
	bool operator<(const edge &e)const{return w<e.w;}
}e[maxn];
int n,m,q,fa[maxn],ch[maxn][2],w[maxn],x[maxn],cnt,ls[maxm*20],rs[maxm*20],rt[maxn];
ll sum[maxm*20],lstans;
bool rev[maxn];
PII mn[maxn],val[maxn];
inline void pushup(int x){
	mn[x]=min(min(mn[ch[x][0]],mn[ch[x][1]]),val[x]);
}
inline void setrev(int x){
	rev[x]^=1;
	swap(ch[x][0],ch[x][1]);
}
inline void pushdown(int x){
	if(rev[x]){
		if(ch[x][0]) setrev(ch[x][0]);
		if(ch[x][1]) setrev(ch[x][1]);
		rev[x]=0;
	}
}
inline bool nroot(int x){
	return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
inline void rotate(int x){
	int y=fa[x],z=fa[y],t=ch[y][1]==x,tt=ch[z][1]==y,w=ch[x][t^1];
	if(w) fa[w]=y;ch[y][t]=w;
	fa[x]=z;if(nroot(y)) ch[z][tt]=x;
	fa[y]=x;ch[x][t^1]=y;
	pushup(y);pushup(x);
}
inline void pushall(int x){
	if(nroot(x)) pushall(fa[x]);
	pushdown(x);
}
inline void splay(int x){
	pushall(x);
	while(nroot(x)){
		int y=fa[x],z=fa[y],t=ch[y][1]==x,tt=ch[z][1]==y;
		if(nroot(y)) rotate(t^tt?x:y);
		rotate(x);
	}
}
void access(int x){
	for(int y=0;x;x=fa[y=x]) splay(x),ch[x][1]=y,pushup(x);
}
void makeroot(int x){
	access(x);splay(x);setrev(x);
}
int findroot(int x){
	access(x);splay(x);
	for(pushdown(x);ch[x][0];pushdown(x)) x=ch[x][0];
	splay(x);
	return x;
}
void split(int x,int y){
	makeroot(x);access(y);splay(y);
}
void link(int x,int y){
	makeroot(x);fa[x]=y;
}
void cut(int x,int y){
	split(x,y);fa[x]=ch[y][0]=0;
}
void build(int &x,int l,int r){
	x=++cnt;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(ls[x],l,mid);
	build(rs[x],mid+1,r);
}
void update(int &x,int y,int l,int r,int p,int v){
	x=++cnt,ls[x]=ls[y],rs[x]=rs[y],sum[x]=sum[y];
	sum[x]+=v;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(mid>=p) update(ls[x],ls[y],l,mid,p,v);
	else update(rs[x],rs[y],mid+1,r,p,v);
}
int query(int x,int l,int r,int ql,int qr){
	if(!x) return 0;
	if(l>=ql && r<=qr) return sum[x];
	int mid=(l+r)>>1;
	if(mid<ql) return query(rs[x],mid+1,r,ql,qr);
	if(mid>=qr) return query(ls[x],l,mid,ql,qr);
	return query(ls[x],l,mid,ql,qr)+query(rs[x],mid+1,r,ql,qr);
} 
void clear(){
	FOR(i,1,cnt) ls[i]=rs[i]=sum[i]=0;
	FOR(i,1,m) rt[i]=x[i]=0;
	cnt=0;
	FOR(i,0,n+m) fa[i]=ch[i][0]=ch[i][1]=rev[i]=0,val[i]=mn[i]=MP(0,0);
	lstans=0;
}
void solve(){
	n=read();m=read();
	FOR(i,1,m) e[i].u=read(),e[i].v=read(),e[i].w=read();
	sort(e+1,e+m+1);
	FOR(i,0,n) mn[i]=val[i]=MP(1e9,i);
	FOR(i,n+1,n+m) mn[i]=val[i]=MP(w[i-n]=e[i-n].w,i);
	FOR(i,1,m){
		int u=e[i].u,v=e[i].v;
		makeroot(u);
		if(findroot(v)==u){
			split(u,v);
			PII p=mn[v];
			int id=p.second-n,uu=e[id].u,vv=e[id].v;
			x[i]=w[id]+1;
			cut(uu,id+n);cut(vv,id+n);
		}
		link(u,i+n);link(v,i+n);
	}
	FOR(i,1,m) update(rt[i],rt[i-1],0,1e6+1,x[i],w[i]);
	q=read();
	while(q--){
		int l=read()-lstans,r=read()-lstans;
		int L=lower_bound(w+1,w+m+1,l)-w;
		int R=lower_bound(w+1,w+m+1,r+1)-w-1;
		if(L>R) printf("%lld\n",lstans=0);
		else printf("%lld\n",lstans=query(rt[R],0,1e6+1,0,l)-query(rt[L-1],0,1e6+1,0,l));
	}
	clear();
}
int main(){
	int T=read();
	while(T--) solve();
}

IK

首先,如果有点没有入度或没有出度,显然无解。

考虑一条链(即除了最后一个点出度都是 \(1\),除了第一个点入度都是 \(1\),还要特判下 \(1\) 号点),它们显然可以看成一体,缩起来。

\(m\le n+20\),所以缩完之后点数不会太多(大概 \(20,21\) 这样)。然后直接开搜。

复杂度大概是出度之积乘个 \(m-n\) 之类的,不会超过 \(O(2^{m-n}(m-n)+n+m)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=111111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,m,el,head[maxn],to[maxn],nxt[maxn],rd[maxn],cd[maxn],cnt,lst[maxn],ans[maxn],al,fa[maxn],son[maxn],tmp[maxn][2],tl,id[maxn];
vector<int> vec[maxn];
bool vis[maxn];
inline void add(int u,int v){
	to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs(int u){
	vis[u]=true;cnt--;
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(v==1 && !cnt){
			ans[1]=1;
			ans[al=2]=u;
			while(ans[al]!=1) ans[al+1]=lst[ans[al]],al++;
			ROF(i,al,2) FOR(j,0,(int)vec[ans[i]].size()-1) printf("%d ",vec[ans[i]][j]);
			puts("1");
			exit(0); 
		}
		if(!vis[v]) lst[v]=u,dfs(v);
	}
	vis[u]=false;cnt++;
}
int main(){
	freopen("king.in","r",stdin);
	freopen("king.out","w",stdout); 
	n=read();m=read();
	FOR(i,1,m){
		int u=read(),v=read();
		add(u,v);
		cd[u]++;rd[v]++; 
	}
	FOR(i,1,n) if(!rd[i] || !cd[i]) return puts("There is no route, Karl!"),0;
	FOR(i,1,n) if(cd[i]==1 && rd[to[head[i]]]==1 && to[head[i]]!=1) fa[to[head[i]]]=i,son[i]=to[head[i]];
	else for(int j=head[i];j;j=nxt[j]) tmp[++tl][0]=i,tmp[tl][1]=to[j]; 
	FOR(i,1,n) if(son[i] && !fa[i]){
		int now=i;
		while(son[now]){
			vec[i].PB(now);
			id[now]=i;
			now=son[now];
		}
		vec[i].PB(now);
		id[now]=i; 
	}
	else if(!id[i]) vec[i].PB(i),id[i]=i;
	FOR(i,1,n) if(!fa[i]) cnt++;
	MEM(head,0);MEM(to,0);MEM(nxt,0);el=0;
	FOR(i,1,tl) add(id[tmp[i][0]],id[tmp[i][1]]);
	dfs(1);
	puts("There is no route, Karl!");
}

LK

注意到环长 \(m=7\) 是质数,也就是说对于每一个环,转到最大值的位置只可能是 \(1\) 个或 \(m\) 个。

如果所有位置都唯一,那么不妨选定一个方向为正方向,并求出每个环要在这个方向上转多少。问题变成给定序列 \(a\),每次选一个区间,可以任意在模 \(m\) 意义下加一个数,变成全 \(0\) 的最小次数。

显然差分,设 \(d_i=(a_i-a_{i-1})\bmod m\)(注意 \(d_{n+1}\) 也要考虑)。那么就是每次选一个 \(d_i\)\(x\),另一个 \(d_j\)\(x\)(都在模意义下)。

注意到对一个数又加又减肯定不优(可以一波操作抵消掉)。

假如我们枚举了哪些是一直加、哪些是一直减。注意要满足所有加的 \((m-d_i)\) 和减的 \(d_i\) 的和相等。而我们知道 \(\sum d_i\) 是多少(显然是 \(m\) 的倍数),所以相当于选择 \(\frac{\sum d_i}{m}\) 个数加,剩下的减。而且任意这么选择都是合法的。

注意到对于一个 \(x\) 个加,\(y\) 个减的子集,若加的 \((m-d_i)\) 和减的 \(d_i\) 的和相等,那么就可以至多 \(x+y-1\) 次操作完成。这启发我们将所有数分成尽量多个这样的组,每个组内操作。而且若有一个操作孤立的组不满足上面这个条件,显然这组不能清零。所以这是充要的。

所以答案就是 \(n+1-cnt\),其中 \(cnt\) 是最大组数。

其实上面那个条件就等价于所有数的和是 \(m\) 的倍数。

显然 \(0\) 每个单独一组,然后 \(1\)\(6\) 组对子,\(2\)\(5\)\(3\)\(4\)。(组对子最优,是因为如果这个数不用来组对子,就必须要用一些和等于另一个数的来凑这组,显然不如一个数就完成这组优。)

(然后我就不会了,看题解去了。发现题解什么都没说。回想了一下我第一次氪 ZROI 的第一场,发现那题跟这题除了有一些多种转法的环,完全一样,甚至 \(m=7\) 都一样,但是当时太自闭了忘掉了。当时我连第一步都不会,但现在我已经做到这一步了,居然卡在只剩三种数这一步,我真的是个 sb。)

然后会发现 \(1\)\(6\) 只剩一种,\(2\)\(5\) 只剩一种,\(3\)\(4\) 只剩一种。

那么就 \(f_{i,j,k}\) 表示最后那三种数现在分别还有 \(i,j,k\) 个(注意不一定满足和是 \(m\) 的倍数),能凑出多少组。

具体的转移是加一个数,然后判断此时的和是不是 \(m\) 的倍数,如果是就能凑出新的一组。

这一部分就可以 \(O(n^3)\) 配很小的常数。

接下来再考虑多种转法的环,就是要为每一个钦定一个最后转到的位置。发现直接忽略它们即可。因为忽略了它们再操作,也可以调整它们最后的位置使得它们恰好归位。而不忽略它们显然也不能让答案更小。

最后时间复杂度 \(O(n^3)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=555,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,m,a[maxn],cnt[7],ans,x,y,z,f[2][maxn][maxn],cur;
char s[10],t[10][10];
inline void chkmax(int &x,int y){if(y>x) x=y;}
inline bool chk(int a,int b,int c){return (x*a+y*b+z*c)%7==0;}
int main(){
	n=read();
	FOR(i,1,n){
		scanf("%s",s);
		bool flag=true;
		FOR(j,1,6) if(s[j]!=s[0]){flag=false;break;}
		if(flag) continue;
		FOR(j,0,6) t[0][j]=s[j];
		FOR(j,1,6){
			t[j][0]=t[j-1][6];
			FOR(k,1,6) t[j][k]=t[j-1][k-1];
		}
		m++;
		FOR(j,1,6) if(strcmp(t[j],t[a[m]])>0) a[m]=j; 
	}
	if(!m) return puts("0"),0;
	FOR(i,1,m+1) cnt[(a[i]-a[i-1]+7)%7]++;
	ans=m+1-cnt[0];
	if(cnt[1]<cnt[6]) swap(cnt[1],cnt[6]),x=6;
	else x=1;
	ans-=cnt[6];cnt[1]-=cnt[6];
	if(cnt[2]<cnt[5]) swap(cnt[2],cnt[5]),y=5;
	else y=2;
	ans-=cnt[5];cnt[2]-=cnt[5];
	if(cnt[3]<cnt[4]) swap(cnt[3],cnt[4]),z=4;
	else z=3;
	ans-=cnt[4];cnt[3]-=cnt[4];
	MEM(f,~0x3f);
	f[0][0][0]=0;
	FOR(i,0,cnt[1]){
		FOR(j,0,cnt[2]) FOR(k,0,cnt[3]) f[cur^1][j][k]=-1e9;
		FOR(j,0,cnt[2]) FOR(k,0,cnt[3]){
			chkmax(f[cur^1][j][k],f[cur][j][k]+chk(i+1,j,k));
			chkmax(f[cur][j+1][k],f[cur][j][k]+chk(i,j+1,k));
			chkmax(f[cur][j][k+1],f[cur][j][k]+chk(i,j,k+1));
		}
		cur^=1;
	}
	ans-=f[cur^1][cnt[2]][cnt[3]];
	printf("%d\n",ans);
}

RG

又是普及难度?

找出每个点在根的哪个儿子的子树中,最小边数显然是儿子个数。

至于被断掉的点数,就是对于同一子树里的求 LCA。这个就用 DFS 序的最大值和最小值两个点就够了。

时间复杂度 \(O(n+q\log n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=111111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,q,el,head[maxn],to[maxn],nxt[maxn],fa[maxn],dep[maxn],sz[maxn],siz[maxn],dfn[maxn],cnt,son[maxn],top[maxn],id[maxn],ans1,ans2;
char op[10];
struct cmp{
	bool operator()(int x,int y){
		return dfn[x]<dfn[y];
	}
};
set<int,cmp> s[maxn];
inline void add(int u,int v){
	to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs1(int u){
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		dep[v]=dep[u]+1;
		dfs1(v);
		sz[u]+=sz[v];
		siz[u]+=siz[v];
		if(sz[v]>sz[son[u]]) son[u]=v;
	}
	sz[u]++;
	if(!siz[u]) siz[u]++;
}
void dfs2(int u,int topf){
	top[u]=topf;
	dfn[u]=++cnt;
	id[cnt]=u;
	if(son[u]) dfs2(son[u],topf);
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(v==son[u]) continue;
		dfs2(v,v);
	}
}
int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
int find(int u){
	while(top[u]!=1){
		if(fa[top[u]]==1) return top[u];
		u=fa[top[u]];
	}
	return son[1];
}
int main(){
	freopen("gangsters.in","r",stdin);
	freopen("gangsters.out","w",stdout);
	n=read();q=read();
	FOR(i,2,n) add(fa[i]=read(),i);
	dfs1(1);dfs2(1,1);
	while(q--){
		scanf("%s",op);
		int u=read();
		if(op[0]=='+'){
			int v=find(u);
			if(s[v].empty()) ans1++;
			else ans2-=siz[lca(*s[v].begin(),*s[v].rbegin())];
			s[v].insert(u);
			ans2+=siz[lca(*s[v].begin(),*s[v].rbegin())];
			ans2--;
		}
		else{
			int v=find(u);
			ans2-=siz[lca(*s[v].begin(),*s[v].rbegin())];
			s[v].erase(u);
			if(s[v].empty()) ans1--;
			else ans2+=siz[lca(*s[v].begin(),*s[v].rbegin())];
			ans2++;
		}
		printf("%d %d\n",ans1,ans2);
	}
}
posted @ 2020-10-15 20:55  ATS_nantf  阅读(109)  评论(0编辑  收藏  举报