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;
}

浙公网安备 33010602011771号