2025 集训记录(7.8~7.13)

洛谷 P9902

传送门

最大流转最小割。
然后设 \(dp_{i,S}\) 表示 DP 到第 \(i\) 个点,\(i-k+1\)\(i\) 哪些被分配到 \(T\) 一侧的最小割。
预处理后 DP 做到 \(2^kn\) 是容易的。 注意只需要计算 \(S\)\(T\) 的贡献,多算了 \(T\)\(S\) 会喜提 90pts。

code

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=8e4+5,K=(1<<7)+5,INF=1e9,mod=1e9+7;
int t,n,m,k;
vector<pii> fr[N];
int dp[N][K],f[N][K],g[10];

int main()
{
	scanf("%d%d%d",&n,&m,&k);
	int u,v,w;
	for(int i=1;i<=m;++i) {
		scanf("%d%d%d",&u,&v,&w);
		if(u!=v) fr[v].epb(u,w);
	}
	int all=(1<<k)-1;
	for(int j=0;j<(1<<k);++j) {
		dp[1][j]=INF;
	}
	for(int i=1;i<=n;++i) {
		for(int j=0;j<=k;++j) g[j]=0;
		for(auto it:fr[i]) {
			g[i-it.fi-1]+=it.se;
		}
		for(int j=1;j<(1<<k);++j) {
			int lw=__builtin_ctz(j);
			f[i][j]=f[i][j^(1<<lw)]+g[lw]; 
		}
	}
	dp[1][0]=0;
	for(int i=2;i<=n;++i) {
		for(int j=0;j<(1<<k);++j) {
			dp[i][j]=INF;
		}
		for(int j=0;j<(1<<k);++j) {
			if(i!=1) {
				int to=(j<<1|1)&all;
				dp[i][to]=min(dp[i][to],dp[i-1][j]+f[i][all]-f[i][j]);
			}
			
			if(i!=n) {
				int to=(j<<1)&all;
				dp[i][to]=min(dp[i][to],dp[i-1][j]);
			} 
		}
	}
	int ans=INF;
	for(int i=0;i<(1<<k);++i) {
		if(i&1) ans=min(ans,dp[n][i]);
	} 
	printf("%d\n",ans);
	return 0;
}

CF1117G

传送门

题意翻译:对区间 \([l,r]\) 建笛卡尔树,问树上所有点代表的区间大小之和。

观察注意到,设 \(ls_i\) 和 $rs_i 表示 \(a_i\) 左侧/右侧第一个大于 \(a_i\) 的数,这是两遍单调栈容易求出的,那么答案可以表示为:

\(\sum_{i=l}^{r}(i-max(ls_i,l-1)+min(rs_i,r+1)-i)+r-l+1\)

\(\sum_{i=l}^{r}min(rs_i,r+1)-\sum_{i=l}^{r}max(ls_i,l-1)+r-l+1\)

\(\sum_{i=l}^{r}min(rs_i,r+1)\) 为例,求这个东西的方法就是将询问挂在 \(r\) 上然后从左到右扫上一遍,并再维护一个单调栈。可以发现到 \(r\) 为止满足 \(rs_i>r\) 的那些点就是此时还在栈中的点。那么再上一个树状数组随着单调栈动态修改,最后区间查询即可知道所有 \(rs_i>r\) 的不合法 \(rs_i\) 并更换成 \(r\)

\(\sum_{i=l}^{r}max(ls_i,l-1)\) 的求法同理。将询问挂在左端点再扫即可。

你可以发现这个做法总共跑了四次单调栈。猎奇。

code

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
#define fun(l,r) (1ll*(l+r)*(r-l+1)/2ll)
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=1e9+7;
int t,n,q;
int pre[N],suf[N];
int a[N],ls[N],rs[N],l[N],r[N];ll sml[N],smr[N];
int stk[N],top=0;vector<pii> sl[N],sr[N];
ll ans[N],tr[N];

void clear() {
	for(int i=1;i<=n;++i) tr[i]=0;
}

void add(int u,int x) {
	for(int i=u;i<=n;i+=i&(-i)) tr[i]+=x;
}

ll query(int l,int r) {
	ll res=0;
	for(int i=l-1;i!=0;i-=i&(-i)) res-=tr[i];
	for(int i=r;i!=0;i-=i&(-i)) res+=tr[i];
	return res;
}

int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i) {
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=q;++i) {
		scanf("%d",&l[i]);
	}
	for(int i=1;i<=q;++i) {
		scanf("%d",&r[i]);
		sr[r[i]].epb(l[i],i),sl[l[i]].epb(r[i],i);
	}
	top=0,stk[0]=0;
	for(int i=1;i<=n;++i) {
		while(top&&a[i]>a[stk[top]]) --top;
		ls[i]=stk[top],stk[++top]=i;
		sml[i]=sml[i-1]+ls[i];
	}
	top=0,stk[0]=n+1;
	for(int i=n;i>=1;--i) {
		while(top&&a[i]>a[stk[top]]) --top;
		rs[i]=stk[top],stk[++top]=i;
		smr[i]=smr[i+1]+rs[i];
	}
	top=0,stk[0]=0;
	for(int i=1;i<=n;++i) {
		while(top&&a[i]>a[stk[top]]) {
			int u=stk[top];add(u,-rs[u]),--top;
		}
		stk[++top]=i,add(i,rs[i]);
		for(auto it:sr[i]) {
			int L=it.fi,le=1,re=top,cnt=top+1;
			while(le<=re) {
				int mid=le+re>>1;
				if(stk[mid]>=L) re=mid-1,cnt=mid;
				else le=mid+1;
			}
			ans[it.se]+=smr[L]-smr[i+1]+1ll*(top-cnt+1)*(i+1)-query(L,i)-fun(L,i);
		}
	}
	
	top=0,stk[0]=n+1,clear();
	for(int i=n;i>=1;--i) {
		while(top&&a[i]>a[stk[top]]) {
			int u=stk[top];add(u,-ls[u]),--top;
		}
		stk[++top]=i,add(i,ls[i]);
		for(auto it:sl[i]) {
			int R=it.fi,le=1,re=top,cnt=top+1;
			while(le<=re) {
				int mid=le+re>>1;
				if(stk[mid]<=R) re=mid-1,cnt=mid;
				else le=mid+1;
			}
			ans[it.se]+=fun(i,R)-(sml[R]-sml[i-1]+1ll*(top-cnt+1)*(i-1)-query(i,R));
		}
	}
	for(int i=1;i<=q;++i) printf("%lld ",ans[i]-(r[i]-l[i]+1));
	return 0;
}

CF1034D

传送门

小清新的 ODT 练习题。*3500 题解最看得懂的一集,老外不会 DS!

首先考虑另外一个类似的问题:多次询问每次求 \([l,r]\) 中区间的并集大小。

考虑离线,然后将所有询问挂在 \(r\) 上面并维护每个 \(l\) 的答案。我们考虑维护每个点最后覆盖到它的线段 \(lst\),询问时问题就变成了 \(lst\) 值小于等于 \(l\) 的点数量,扫到线段 \(i\) 的修改就是区间推平为 \(i\)

如果扫到的第 \(i\) 条线段覆盖到了某个点,那么显然左端点位于 \([lst+1,i]\) 的询问答案全部加了 \(1\)。使用 ODT 维护每个点+线段树维护左端点答案,这个问题容易做到 \(O(n(\log\ n+\log\ v))\)


现在考虑原问题。首先注意到前 \(k\) 大,所以直接二分。

设枚举到 \(i\) 时左端点 \(l\) 的答案为 \(f_i(l)\),那么这个函数是单调不增的。所以如果沿用上述做法的话容易线段树二分做到 \(O(n\ \log\ n\ \log v)\)

优化到一只 \(\log\) 的话考虑我们一直在做区间加,所以 \(f_i(l)\ge mid\) 的分界点也在一直右移,所以在二分外预先用 ODT 求出所有修改,二分内差分+双指针容易做到 \(O(n\ \log\ v)\),不需要线段树了。

code

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<ll,ll>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=3e5+5,INF=1e9,mod=1e9+7;
int t,n,k;

struct node {
	int l,r;
}a[N];

struct ODT {
	int l,r;mutable int v;
	bool operator<(const ODT &W) const {
		return l<W.l;
	}
};set<ODT> odt;
int f[N];vector<pii> s[N];

auto spilt(int x) {
	auto it=--odt.upper_bound({x,0,0});
	if(x==it->l) return it;
	int l=it->l,r=it->r,v=it->v;
	odt.erase(it),odt.insert({l,x-1,v});
	return odt.insert({x,r,v}).fi;
}

void assign(int l,int r,int x) {
	auto itr=spilt(r+1),itl=spilt(l);
	while(itl!=itr) {
		int l=itl->l,r=itl->r,v=itl->v;
		s[x].epb(v+1,r-l+1);
		itl=odt.erase(itl);
	}
	odt.insert({l,r,x});
}

pii check(int mid) {
	ll res=0,sum=0,cnt=0;int ps=0,nw=0;
	for(int i=1;i<=n;++i) f[i]=0;
	for(int i=1;i<=n;++i) {
		for(auto it:s[i]) {
			int p=it.fi,v=it.se;
			if(p<=ps) nw+=v,sum+=1ll*(ps-p+1)*v;
			f[p]+=v,f[i+1]-=v;
		}
		while(ps<n&&nw+f[ps+1]>=mid) {
			++ps,nw+=f[ps],sum+=nw;
		}
		cnt+=ps,res+=sum;
	}
	return {cnt,res};
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i) {
		scanf("%d%d",&a[i].l,&a[i].r);--a[i].r;
	}
	odt.insert({1,INF,0});
	for(int i=1;i<=n;++i) {
		assign(a[i].l,a[i].r,i);
	}
	ll l=1,r=INF,ans=0;
	while(l<=r) {
		int mid=l+r>>1;pii res=check(mid);
		if(res.fi>=k) {
			ans=res.se-(res.fi-k)*mid,l=mid+1;
		}
		else r=mid-1;
	}
	printf("%lld\n",ans);
	return 0;
}

CF547D

传送门

做过的题。这么典的构造怎么评的 *2600。

第一种做法(二分图):

考虑将每行每列中的点两两随机匹配连边。匹配可能剩下的都不管。

易证得到的是二分图。直接跑二分图染色即可构造出方案。

第二种做法(欧拉回路):

这是经典套路了。考虑将问题转换成行列之间连边,现在要钦定边的方向使得所有点入度出度之差不大于 \(1\)

将所有奇数点都向一个虚点连边,得到一个所有点都是偶数的图,然后直接欧拉回路,由欧拉回路性质可得入度=出度,不算虚边就是入度出度之差不大于 \(1\)

code

由于第二种做法太典,这里是第一种做法。

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define popcnt __builtin_popcount
#define bstring basic_string
#define all(x) x.begin(),x.end()
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,INF=2e9,mod=1e9+7;
int t,n,m=0;
int x[N],y[N];
vector<int> sx[N],sy[N];
vector<int> e[N];
int col[N];

void add(int u,int v) {
	e[u].epb(v),e[v].epb(u);
}

void dfs(int u,int Col) {
	col[u]=Col;
	for(auto to:e[u]) {
		if(!col[to]) dfs(to,3-Col);
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i) {
		scanf("%d%d",&x[i],&y[i]);
		m=max({m,x[i],y[i]});
		sx[x[i]].epb(i),sy[y[i]].epb(i);
	}
	for(int i=1;i<=m;++i) {
		for(int j=1;j<sx[i].size();j+=2) {
			add(sx[i][j-1],sx[i][j]);
		}
		for(int j=1;j<sy[i].size();j+=2) {
			add(sy[i][j-1],sy[i][j]);
		}
	}
	for(int i=1;i<=n;++i) {
		if(!col[i]) dfs(i,1);
	}
	for(int i=1;i<=n;++i) {
		putchar((col[i]==1)?'r':'b');
	}
	puts("");
	return 0;
}

洛谷 P3749

传送门

网络流还是遇到的太少了。第一次做最大权闭合子图一类题。

最大权闭合子图就是,一个有向图,每个点有权值(可负),然后选了一个点那么连向的必须全选,求最大的选的和。

解决办法就是正权点连 \(S\),边权就是点权,负权点连 \(T\),边权为点权取反。然后原图中的边统统连上,边权无穷大。答案为正权点之和-最小割。

这样的话割正权点的边意思就是舍弃,割负权点意思就是选上。如果存在边 \(u\to v\),其中 \(u\) 正权且选了 \(v\) 负权且没选,那么网络流中的图就连通了,不合法。于是这个问题解决了。

回到原问题,首先选了 \(d_{i,j}\)\(d_{i+1,j}\)\(d_{i,j-1}\) 都要选,连了。

然后每选一个代号 \(x\) 都要造成 \(x\) 的代价,于是直接令 \(d_{i,i}\) 减去 \(a_i\)

最后每个类别一旦被选就造成 \(mx^2\) 的代价,于是 \(d_{i,i}\) 再向代号 \(a_i\) 连相应边权的边。

最后每个点根据正负分别向 \(S\)\(T\) 连边。跑一遍 Dinic 就做完了。

code

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=105,M=3e5+5,INF=2e9,mod=1e9+7;
int n,m,mx=0,s=0,t=0,sum=0;
int d[N][N],id[N][N],a[N],idx=0;

struct edge {
	int ne,to,w;
}e[M<<1];
int head[M],tot=1;
int dis[M],cur[M];

void add(int u,int v,int w) {
	e[++tot]={head[u],v,w},head[u]=tot;
	e[++tot]={head[v],u,0},head[v]=tot;
}

bool bfs() {
	queue<int> q;
	for(int i=s;i<=t;++i) {
		dis[i]=-1,cur[i]=head[i];
	}
	q.push(s),dis[s]=1;
	while(!q.empty()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].ne) {
			int to=e[i].to;
			if(e[i].w&&dis[to]==-1) {
				dis[to]=dis[u]+1;
				if(to==t) return 1;
				q.push(to);
			}
		}
	}
	return 0;
}

int dfs(int u,int flow) {
	if(u==t) return flow;
	int used=0;
	for(int i=cur[u];i;i=e[i].ne) {
		int to=e[i].to;cur[u]=i;
		if(dis[u]+1==dis[to]&&e[i].w) {
			int res=dfs(to,min(flow,e[i].w));
			flow-=res,used+=res;
			e[i].w-=res,e[i^1].w+=res;
			if(!flow) break;
		}
	}
	if(!used) dis[u]=-1;
	return used;
}

int Dinic() {
	int res=0;
	while(bfs()) res+=dfs(s,INF);
	return res;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		scanf("%d",&a[i]);
		mx=max(mx,a[i]);
	} 
	for(int i=1;i<=n;++i) {
		for(int j=i;j<=n;++j) {
			scanf("%d",&d[i][j]);
			if(i==j) d[i][j]-=a[i];
			id[i][j]=++idx;
		}
	}
	t=idx+mx+10;
	for(int i=1;i<=n;++i) {
		add(id[i][i],idx+a[i],INF);
	}
	for(int i=1;i<=mx;++i) {
		if(m) add(idx+i,t,m*i*i);
	}
	for(int i=1;i<=n;++i) {
		for(int j=i;j<=n;++j) {
			if(d[i][j]>=0) {
				add(s,id[i][j],d[i][j]),sum+=d[i][j];
			}
			else add(id[i][j],t,-d[i][j]);
			if(i!=j) {
				add(id[i][j],id[i+1][j],INF);
				add(id[i][j],id[i][j-1],INF);
			}
		}
	}
	printf("%d\n",sum-Dinic());
	return 0;
}

AGC037D

传送门

观察注意到,如果设最后的 \(D\) 矩阵每一行的数为一个颜色,那么 \(B\) 矩阵满足 \(m\) 列中每列都出现 \(n\) 个颜色,\(C\) 矩阵只需由 \(B\) 矩阵每列排序后得到。

考虑对 \(A\)\(n\) 行和 \(n\) 个颜色建立点,每行对每个出现的颜色连出现次数条边。那么对这个图的一个二分图完美匹配就就可以构造出 \(B\) 的一列。考虑到这个图是一个每个点度数为 \(m\) 的二分图,由 Hall 定理得这个图一定扛得住 \(m\) 次完美匹配。于是就成功构造出了 \(B\) 矩阵。

code

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=105,INF=2e9,mod=1e9+7;
int t,n,m;
int a[N][N],b[N][N],c[N][N],used[N][N],e[N][N],all[N];
int vis[N],p[N],tim=0;

bool match(int u) {
	for(int i=1;i<=n;++i) {
		if(e[u][i]&&vis[i]!=tim) {
			vis[i]=tim;
			if(!p[i]||match(p[i])) {
				p[i]=u;return 1;
			}
		}
	}
	return 0;
} 

void Print(int a[N][N]) {
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			printf("%d ",a[i][j]);
		}
		puts("");
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			scanf("%d",&a[i][j]);
			++e[i][(a[i][j]-1)/m+1];
			//cr2(i,(a[i][j]-1)/m+1) 
		}
	}
	for(int i=1;i<=m;++i) {
		for(int j=1;j<=n;++j) p[j]=0; 
		for(int j=1;j<=n;++j) {
			++tim;
			assert(match(j));
		}
		for(int j=1;j<=n;++j) {
			int u=p[j];
			for(int k=1;k<=m;++k) {
				if((a[u][k]-1)/m+1==j&&!used[u][k]) {
					used[u][k]=1,b[u][i]=a[u][k];
					break;
				}
			}
			--e[u][j];
		}
	}
	for(int j=1;j<=m;++j) {
		for(int i=1;i<=n;++i) all[i]=b[i][j];
		sort(all+1,all+1+n);
		for(int i=1;i<=n;++i) c[i][j]=all[i];
	}
	Print(b),Print(c);
	return 0;
}

7.12 模拟赛 T2

T3 太史了不想补。

题意

给出数组 \(a_u\) 和一棵树,定义从点 \(u\) 移动到到其祖先点 \(v\) 的代价为 \(a_u+dis(u,v)^{1.5}\)。对于每个 \(i\) 求出一直移动到点 \(1\) 的最小代价。\(n\le 10^6\)

sol

其中一个部分分是链。根据 \(dis(u,v)^{1.5}\) 的函数特点得到 DP 决策具有单调性,直接上一个二分队列即可解决。

扩展到树上考虑再加一个叫链分治的东西,统计重链对轻子树的贡献:具体地,重链加上某个点后,直接枚举轻子树并算上这条重链前缀的贡献(在队列上二分即可)。

时间复杂度 \(O(n\log^2n)\)。有一车人乱搞通过此题,谴责了。

code

#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,mod=1e9+7;
const double INF=1e18;
int n,m;
double a[N],f[N];vector<int> e[N];
int sz[N],son[N],dfn[N],rnk[N],dep[N],idx=0;
int q[N],k[N],h=1,t=0;

#define get(x,y) (f[x]+1.0*(y-dep[x])*sqrt(y-dep[x]))

int binary(int x,int y) {
	int l=dep[y],r=n,res=n+1,mid;
	while(l<=r) {
		mid=l+r>>1;
		if(get(x,mid)>=get(y,mid)) res=mid,r=mid-1;
		else l=mid+1;
	}
	return res;
}

void dfs(int u) {
	sz[u]=1,dfn[u]=++idx,rnk[idx]=u;
	for(auto to:e[u]) {
		dep[to]=dep[u]+1,dfs(to),sz[u]+=sz[to];
		if(!son[u]||sz[to]>sz[son[u]]) son[u]=to;
	}
} 

void dfs2(int u) {
	while(h<t&&k[h]<=dep[u]) ++h;
	if(h<=t) f[u]=min(f[u],get(q[h],dep[u])+a[u]);
	while(h<t&&get(q[t],k[t-1])>get(u,k[t-1])) --t;
	k[t]=binary(q[t],u),q[++t]=u; 
	
	for(auto to:e[u]) {
		if(to==son[u]) continue;
		for(int i=dfn[to];i<dfn[to]+sz[to];++i) {
			int u=rnk[i],l=h,r=t-1,p=h;
			while(l<=r) {
				int mid=l+r>>1;
				if(k[mid]<=dep[u]) l=p=mid+1;
				else r=mid-1;
			}
			if(p<=t) f[u]=min(f[u],get(q[p],dep[u])+a[u]);
		}
	}
	
	if(son[u]) dfs2(son[u]);
	
	for(auto to:e[u]) {
		if(to!=son[u]) {
			h=1,t=0,dfs2(to);
		}
	}
}

int main()
{
	freopen("onepointfive.in","r",stdin);
	freopen("onepointfive.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i) {
		scanf("%lf",&a[i]);
		f[i]=INF;
	}
	int u,chk=1,chk2=1;
	for(int i=2;i<=n;++i) {
		scanf("%d",&u);
		e[u].epb(i);
	}
	dep[1]=1,f[1]=0,dfs(1),dfs2(1);
	for(int i=1;i<=n;++i) {
		printf("%.4lf ",f[i]);
	}
	puts("");
	return 0;
}

CF825G

传送门

很难评的 *2500。

以第一个黑点为根 dfs 一遍求出每个点到根路径的最小编号 \(a_i\)。记 \(ans=\min\limits_{col_u=1}a_u\),那么一次询问的答案为 \(\min(ans,a_x)\)

感性理解是容易的。

code

#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=1e9+7;
int t,n,q;
int col[N];vector<int> e[N];
int a[N];

void dfs(int u,int fa) {
	a[u]=min(u,a[fa]);
	for(auto to:e[u]) {
		if(to!=fa) dfs(to,u);
	}
}

int main()
{
	scanf("%d%d",&n,&q);
	int u,v;
	for(int i=1;i<=n-1;++i) {
		scanf("%d%d",&u,&v);
		e[u].epb(v),e[v].epb(u);
	}
	int op,x,fst=1,ans=INF,lst=0;
	while(q--) {
		scanf("%d%d",&op,&x);
		x=(x+lst)%n+1;
		if(op==1) {
			if(fst) a[0]=INF,dfs(x,0),fst=0;
			ans=min(ans,a[x]);
		}
		else {
			printf("%d\n",lst=min(a[x],ans));
		}
	}
	return 0;
}

CF2002F2

传送门

比上一道更猎奇的 *2800 乱搞题。

正经做法是找到两个数 \(p,q\) 保证答案在 \([p,n]*[q,m]\) 之内,然后 DP 即可。由于这两个数特别接近 \(n,m\),不妨开放一点,范围直接取 \([n-B,n]*[m-B,m]\)\(B\) 可取 \(120\)。(如果是在场上遇到就直接打表观察。)

code

#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=125,B=120,INF=2e9,mod=1e9+7;
int t,n,m,l,f;
int dp[N][N];

void sol() {
	scanf("%d%d%d%d",&n,&m,&l,&f);
	int u=max(n-B,1),v=max(m-B,1);ll ans=0;
	for(int i=0;i<=n-u;++i) {
		for(int j=0;j<=m-v;++j) {
			if(!i||!j) dp[i][j]=1;
			else if(__gcd(i+u,j+v)<=1) {
				dp[i][j]=dp[i-1][j]|dp[i][j-1];
			}
			else dp[i][j]=0;
			if(dp[i][j]) ans=max(ans,1ll*(i+u)*l+1ll*(j+v)*f);
		}
	}
	printf("%lld\n",ans);
}

int main()
{
	scanf("%d",&t);
	while(t--) {
		sol();
	}
	return 0;
}

7.11 模拟赛 T3

补题补题。对拍过程中拿一份 0pts 代码先后叉掉 \(3\) 人的 AC 代码。data too water。

题意

你有 \(s\) 个球和 \(n+2\) 个盒子,编号 \(0\)\(n+1\),盒子有容量 \(a_i\)

\(q\) 次询问,给定区间 \([l,r]\),定义 \(f_i(l\le i\le r)\) 表示初始小球全部在盒子 \(i\),每次可以花费 \(1\) 的代价移动到相邻盒子,假定 \([l,r]\) 之外的盒子容量无限大,\(f_i\) 即为最终每个球要么移动到 \([l,r]\) 外要么在 \([l,r]\) 内且容量没爆的最小代价。每次询问要求 \(\max f_i\)

\(n\le 2\times 10^5,q\le 10^6\)

sol

首先 \(nq\) 的暴力是容易的。直接前缀和容易查询 \(f_i\) 做到 \(O(1)\)

正解考虑先将询问从 \(mid\) 对半开,显然 \([l,mid]\) 中球最后只会触及到 \(l\) 这一侧的边界。

然后我们注意到随着 \(l\) 的增大,最优决策点有决策单调性。所以直接考虑从左到右枚举决策点并维护二分队列,扫到一个 \(mid\) 时直接对 \(l\) 二分找最优决策点。

\((mid,r]\) 一侧同理。

code

#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,M=1e6+5,mod=1e9+7;
const ll INF=1e18;
int n,m;ll s;
ll a[N]; 
vector<pii> sl[N],sr[N];
ll ans[M],s1[N],s2[N],f[N];int _d[N];

inline int find(int x) {
	int l=1,r=min(n-x+1,x),res=0;
	while(l<=r) {
		int mid=l+r>>1;
		if(s1[x+mid-1]-s1[x-mid]<s) {
			res=mid,l=mid+1;
		} 
		else r=mid-1;
	}
	return res;
}

inline ll get(int x,int d,int op=0) {
	if(!op&&d>=_d[x]) return f[x]; 
	int l=x-d+1,r=x+d-1;ll res=0;
	res+=(s1[x-1]-s1[l-1])*x-(s2[x-1]-s2[l-1]);
	res+=(s2[r]-s2[x])-(s1[r]-s1[x])*x;
	res+=(s-(s1[r]-s1[l-1]))*d;
	return res;
}

inline int findl(int x,int y) {
	int l=1,r=x,res=n+1;
	while(l<=r) {
		int mid=l+r>>1;
		if(get(y,y-mid+1)>=get(x,x-mid+1)) {
			r=mid-1,res=mid;
		}
		else l=mid+1;
	}
	return res;
}

inline int findr(int x,int y) {
	int l=y,r=n,res=0;
	while(l<=r) {
		int mid=l+r>>1;
		if(get(x,mid-x+1)>=get(y,mid-y+1)) {
			l=mid+1,res=mid;
		}
		else r=mid-1;
	}
	return res;
}

int q[N],k[N],h=1,t=0;

int main()
{
	freopen("ball.in","r",stdin);
	freopen("ball.out","w",stdout);
	scanf("%d%lld",&n,&s);
	for(int i=1;i<=n;++i) {
		scanf("%lld",&a[i]);
		s1[i]=s1[i-1]+a[i];
		s2[i]=s2[i-1]+a[i]*i; 
	}
	for(int i=1;i<=n;++i) {
		_d[i]=find(i),f[i]=get(i,_d[i],1);
	}
	scanf("%d",&m);int l,r;
	for(int i=1;i<=m;++i) {
		scanf("%d%d",&l,&r);
		int ml=(l+r)>>1,mr=(l+r+1)>>1;
		sl[ml].epb(l,i),sr[mr].epb(r,i);
	}
	for(int i=1;i<=n;++i) {
		while(h<t&&get(i,i-k[t-1]+1)>=get(q[t],q[t]-k[t-1]+1)) --t;
		if(h<=t) k[t]=findl(q[t],i);q[++t]=i;
		for(auto it:sl[i]) {
			int l=h,r=t-1,res=h;
			while(l<=r) {
				int mid=l+r>>1;
				if(k[mid]<=it.fi) l=res=mid+1;
				else r=mid-1;
			}
			ans[it.se]=max(ans[it.se],get(q[res],q[res]-it.fi+1));
		} 
	}
	h=1,t=0;
	for(int i=n;i>=1;--i) {
		while(h<t&&get(i,k[t-1]-i+1)>=get(q[t],k[t-1]-q[t]+1)) --t;
		if(h<=t) k[t]=findr(i,q[t]);q[++t]=i;
		for(auto it:sr[i]) {
			int l=h,r=t-1,res=h;
			while(l<=r) {
				int mid=l+r>>1;
				if(k[mid]>=it.fi) l=res=mid+1;
				else r=mid-1;
			}
			ans[it.se]=max(ans[it.se],get(q[res],it.fi-q[res]+1));
		} 
	}
	for(int i=1;i<=m;++i) {
		printf("%lld\n",ans[i]);
	}
	return 0;
}

UOJ938

传送门

首先将问题转换成将一个点 \(u\) 的若干个邻居断边后加点连起来。便于理解。

注意到一段操作的先后顺序和最后树的形态是无关的。所以考虑选定一个根后自底向上 DP。

\(f_i\) 表示变出子树 \(i\) 所需的最小满二叉树。考虑转移:

1.\(i\) 是叶子,那么 \(f_i=1\)

2.\(i\) 只有一个儿子,那么显然是将另一个分支删完了,我们有 \(f_i=f_v+1\)

3.\(i\) 有多个儿子,\(f_i=\left \lceil \log_2(\sum_{v\in son(u)}2^{f_v}) \right \rceil\)

换根的话直接做比较复杂,一个贪心是挑选 \(f_v\) 最小的儿子往下递归,容易感性理解。这样好写而且复杂度是优秀的 \(O(n)\)

code

有细节,但不多。

#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=1e9+7;
int t,n,m,ans=INF;
vector<int> e[N];
int f[N],g[N],mx[N];
int cnt[N];vector<int> cl;

int get(vector<int> &q) {
	int res=0,sum=0;cl.clear();
	for(auto it:q) {
		cl.epb(it),res=max(res,it);
		++cnt[it],++sum;
		while(cnt[it]==2) {
			cl.epb(it+1),res=max(res,it+1);
			cnt[it]=0,++cnt[it+1],--sum,++it;
		}
	}
	for(auto it:cl) cnt[it]=0;
	return res+(sum>1);
}

void dfs(int u,int fa) {
	vector<int> s;
	for(auto to:e[u]) {
		if(to==fa) continue;
		dfs(to,u),s.epb(f[to]);
		if(!mx[u]||f[to]>f[mx[u]]) mx[u]=to;
	}
	if(s.size()<=1) f[u]=f[mx[u]]+1;
	else f[u]=get(s);
}

void dfs2(int u,int fa) {
	vector<int> s;
	for(auto to:e[u]) {
		if(to==fa) continue;
		if(to!=mx[u]) s.epb(f[to]);
	}
	if(g[u]) s.epb(g[u]);
	if(mx[u]) {
		if(e[u].size()<=2) {
			g[mx[u]]=(e[u].size()==1)?1:(s[0]+1);
		}
		else g[mx[u]]=get(s);
		dfs2(mx[u],u);
	}
	if(e[u].size()<=1) {
		ans=min(ans,(fa==-1)?f[u]:(g[u]+1));
	}
	else {
		s.epb(f[mx[u]]),ans=min(ans,get(s));
	}
}

void sol() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) {
		f[i]=1,g[i]=mx[i]=0,e[i].clear();
	}
	int u,v;ans=INF;
	for(int i=1;i<=n-1;++i) {
		scanf("%d%d",&u,&v);
		e[u].epb(v),e[v].epb(u);
	}
	dfs(1,-1),g[1]=0,dfs2(1,-1);
	printf("%d\n",ans);
}

int main()
{
	scanf("%d",&t);
	while(t--) {
		sol();
	}
	return 0;
}

CF1270H

传送门

这题不是比 CF1034D 还小清新?为啥没人写?

小清新线段树练习题。

做过 ARC187B 的人都知道,求连通块的数量等同于求满足 \(\min\limits_{1\le j\le i}a_j>\max\limits_{i+1\le j\le n}a_j\) 的分界点 \(i\) 数量。

考虑枚举 \(\max\limits_{i+1\le j\le n}a_j\) 记为 \(v\),然后参照 P2757 的套路,设数组中大于等于 \(v\) 的为 \(1\),小于等于 \(v\) 的为 \(0\)。那么 \(v\) 能作为分界点仅当序列形如 \(111...10...000\)。如果强制令 \(a_0=inf\)\(a_{n+1}=-inf\),那么条件等价于相邻且不同有且只有 \(1\) 对。

考虑直接用线段树维护每个值扩展出的序列中相邻且不同的对数,具体地,相邻的 \(a_i\)\(a_{i+1}\) 会对 \([\min(a_i,a{i+1}),max(a_i,a{i+1})-1]\)\(1\) 的贡献,同时维护每个元素是否在序列中出现,最后求的是全局满足在序列中出现且值为 \(1\) 的数个数,即最小值个数,这都是简单线段树容易做的。

code

#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e5+5,INF=2e9,mod=1e9+7;
int t,n,m,mx=1e6;
int cnt[N<<1],a[N];

struct tree {
	int l,r,mi,tag,cnt;
}tr[N<<3];

void pushup(int u) {
	tr[u].mi=min(tr[u<<1].mi,tr[u<<1|1].mi);tr[u].cnt=0;
	if(tr[u].mi==tr[u<<1].mi) tr[u].cnt+=tr[u<<1].cnt;
	if(tr[u].mi==tr[u<<1|1].mi) tr[u].cnt+=tr[u<<1|1].cnt;
}

void add(int u,int x) {
	tr[u].mi+=x,tr[u].tag+=x;
}

void pushdown(int u) {
	if(!tr[u].tag) return;
	add(u<<1,tr[u].tag),add(u<<1|1,tr[u].tag);
	tr[u].tag=0;
}

void build(int u,int l,int r) {
	tr[u].l=l,tr[u].r=r,tr[u].cnt=0;
	if(l==r) {
		tr[u].mi=0,tr[u].cnt=cnt[l];
		return;
	}
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}

void update(int u,int l,int r,int x) {
	if(l>r) return; 
	if(l<=tr[u].l&&tr[u].r<=r) {
		return add(u,x);
	}
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid) update(u<<1,l,r,x);
	if(r>mid) update(u<<1|1,l,r,x);
	pushup(u);
}

void update2(int u,int ps,int x) {
	if(tr[u].l==tr[u].r) {
		tr[u].cnt+=x;return;
	}
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(ps<=mid) update2(u<<1,ps,x);
	else update2(u<<1|1,ps,x);
	pushup(u);
}

void change(int i,int x) {
	update(1,min(a[i-1],a[i]),max(a[i-1],a[i])-1,x);
	update(1,min(a[i],a[i+1]),max(a[i],a[i+1])-1,x);
	update2(1,a[i],x);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		scanf("%d",&a[i]);
		++cnt[a[i]];
	}
	build(1,0,mx);a[0]=mx+1,a[n+1]=0;
	for(int i=0;i<=n;++i) {
		update(1,min(a[i],a[i+1]),max(a[i],a[i+1])-1,1);
	}
	int ps,x;
	while(m--) {
		scanf("%d%d",&ps,&x);
		change(ps,-1),a[ps]=x,change(ps,1);
		printf("%d\n",tr[1].cnt);
	}
	return 0;
}

ARC161F

传送门

首先转换成最大权闭合子图问题,设边的权值为 \(1\),点的权值为 \(-D\),选了边必须选两个点。

如果建好图跑网络流跑出来最大权闭合子图大于 \(0\),即存在密度大于 \(D\) 的导出子图,那就直接输出 No。

否则最大权闭合子图必定为 \(0\)(原图和空图)。这时候注意到网络流跑出来的结果正好给每个边定了向,得到一个有向图,每个点的出度都正好为 \(D\)

尝试证明:如果存在密度等于 \(D\) 的真导出子图,那么表现在新得到的有向图中就是 SCC 数大于 \(1\)

  • 充分性(存在多个 SCC 则一定存在满足的导出子图):如果存在多个 SCC,找到没有出边的 SCC,它的边都连向内部,于是密度等于 \(D\)

  • 必要性(存在则这个有向图不是一个 SCC,即存在多个 SCC):显然这个真导出子图没有往外连的任何边(度数都是 \(D\),全部往内部连才能密度为 \(D\))。于是原有向图不是 SCC。

综上,先跑一遍网络流判断是否存在大于 \(D\) 并将边定向,然后跑一遍 tarjan 判断 SCC 数量是否等于 \(1\) 以判断是否存在等于 \(D\) 即可。

code

#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define m(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e5+5,INF=2e9,mod=1e9+7;
int T,n,d,m,s=0,t=0;

struct _edge {
	int u,v;
}_e[N];

struct edge {
	int ne,to,w;
}e[N*10];
int head[N],head2[N],tot=1;
int dis[N],cur[N],st[N];
int dfn[N],low[N],idx=0;
int stk[N],in_stk[N],top=0,scc_cnt=0;

void add(int head[],int u,int v,int w) {
	e[++tot]={head[u],v,w},head[u]=tot;
	if(w) e[++tot]={head[v],u,0},head[v]=tot;
}

bool bfs() {
	queue<int> q;
	for(int i=s;i<=t;++i) {
		cur[i]=head[i],dis[i]=-1;
	}
	dis[s]=1,q.push(s);
	while(!q.empty()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].ne) {
			int to=e[i].to;
			if(e[i].w&&dis[to]==-1) {
				dis[to]=dis[u]+1;
				if(to==t) return 1;
				q.push(to);
			}
		}
	}
	return 0;
}

int dfs(int u,int flow) {
	if(u==t) return flow;
	int used=0;
	for(int i=cur[u];i;i=e[i].ne) {
		cur[u]=i;int to=e[i].to;
		if(e[i].w&&dis[to]==dis[u]+1) {
			int x=dfs(to,min(flow,e[i].w));
			e[i].w-=x,e[i^1].w+=x;
			flow-=x,used+=x;
			if(!flow) break;
		}
	}
	if(!used) dis[u]=-1;
	return used;
}

int Dinic() {
	int res=0;
	while(bfs()) res+=dfs(s,INF);
	return res;
}

void tarjan(int u) {
	dfn[u]=low[u]=++idx;
	stk[++top]=u,in_stk[u]=1;
	for(int i=head2[u];i;i=e[i].ne) {
		int to=e[i].to;
		if(!dfn[to]) {
			tarjan(to);
			low[u]=min(low[u],low[to]);
		}
		else if(in_stk[to]) {
			low[u]=min(low[u],dfn[to]);
		}
	}
	if(low[u]==dfn[u]) {
		int y;++scc_cnt;
		do {
			y=stk[top--];
			in_stk[y]=0;
		}while(y!=u);
	}
}

void sol() {
	scanf("%d%d",&n,&d);
	tot=1,m=n*d,s=0,t=m+n+1;
	for(int i=s;i<=t;++i) head[i]=0;
	for(int i=1;i<=n;++i) {
		dfn[i]=low[i]=in_stk[i]=head2[i]=0;
	}
	for(int i=1;i<=m;++i) {
		scanf("%d%d",&_e[i].u,&_e[i].v);
		add(head,i,m+_e[i].u,INF);
		add(head,i,m+_e[i].v,INF);
		st[i]=0;
	}
	for(int i=1;i<=m;++i) add(head,s,i,1);
	for(int i=m+1;i<=m+n;++i) add(head,i,t,d);
	int res=Dinic();
	if(res!=m) {
		puts("No");return;
	}
	for(int i=m+1;i<=m+1+n;++i) {
		for(int j=head[i];j;j=e[j].ne) {
			if(e[j].to==t) continue;
			int id=(j-2)/4+1;
			if(e[j].w==1) st[id]=i-m;
		}
	}
	for(int i=1;i<=m;++i) {
		if(st[i]==_e[i].v) {
			add(head2,_e[i].v,_e[i].u,0);
		}
		else add(head2,_e[i].u,_e[i].v,0);
	}
	top=scc_cnt=0;
	for(int i=1;i<=n;++i) {
		if(!dfn[i]) tarjan(i);
	}
	puts(scc_cnt>1?"No":"Yes");
}

int main()
{
	scanf("%d",&T);
	while(T--) {
		sol();
	}
	return 0;
}
posted @ 2025-07-21 20:18  Oier_szc  阅读(10)  评论(0)    收藏  举报