6.19训练日记

P1129 [ZJOI2007] 矩阵游戏

链接
网格图是一个经典的二分图模型,在这题中.交换行列实际上就是交换节点的相对编号,但是对原本的边的连接情况仍然不变.
我们规定左部是行,右部是列.
比如说(1,3)上是1.对应着左部编号1连接右部编号3.
如果我们实行行变换,就是把这条对应的左部节点编号1情况换到另外一个节点的编号情况.比如交换行1与行2.
此时变为了(2,3)有1,对应编号2的点连接到右部编号3.
同理,如果交换列了,就是交换右部点的连接情况.
题目有解的情况是有n条这样的边.
(1,1),(2,2),(3,3).
既然我们从行列交换中发现,我们可以交换两个节点的相对编号来实现.行交换就是更改左部点的相对编号,列交换是更改右部的相对编号.
再仔细思考下,如果我们对于这个图含有一个完美匹配的时候,这个网格该是什么样的形状的.
就是存在一种方案,比如说n=3的时候
0 0 1
1 0 0
0 1 0
像这种,人为很容易地就能知道如何更改的情况.
把上面的图用二分图划出来,就会发现,只要通过行列交换,变换起始点与终点的编号,总是能构造出(1,1),(2,2)(3,3)这样的边.
所以,这个问题转化为求是否存在一个完美匹配.
跑网络流即可.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 400+5;
const int INF = 1e9+7;
typedef long long ll;
typedef pair<int,int> pii;
struct Edge{
	int from,to,cap,flow;//起点,终点,容量,流量
};
struct Dicnic{
	int n,m,s,t;//点,边,源点,汇点
	vector<Edge> edges;//边集
	vector<int> G[maxn];
	bool vis[maxn];//bfs 使用的
	int d[maxn];//起点到i的距离,就是bfs得到的层次图,下次dfs沿着d走
	int cur[maxn];//当前弧优化使用
	void addEdge(int from,int to,int cap){
		edges.push_back({from,to,cap,0});
		edges.push_back({to,from,0,0});//反向边
		m = edges.size();
		G[from].push_back(m-2);G[to].push_back(m-1);
		//正边的编号都是偶数,反向边的编号都是奇数
	}
	bool bfs(){
		memset(vis,0,sizeof(vis));
		queue<int> Q;Q.push(s);
		d[s]=0;vis[s]=true;
		while(!Q.empty()){
			int x = Q.front();Q.pop();
			for(auto v : G[x]){
				Edge &e = edges[v];//获得边的编号
				if(!vis[e.to]&&e.cap>e.flow){
					//只有未被访问过,而且位于残量网络中的弧才考虑
					vis[e.to]=1;
					d[e.to]=d[x]+1;Q.push(e.to);
				}
			}
		}
		return vis[t];
	}
        int dfs(int x,int a){
		//寻找增广路,a代表目前经过的路中,每个边剩余的流量
		if(x==t||a==0) return a;//到达终点/残量为0
		int flow = 0,f;
		for(int &i = cur[x];i<G[x].size();i++){
			Edge &e = edges[G[x][i]];
			if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0){
				//首先要按层次图走,然后当前增加流量要是增广路中流量最小的的
				e.flow+=f;
				edges[G[x][i]^1].flow-=f;//正边加流量,反边减流量
				flow+=f;a-=f;
				if(a==0) break;
			}
		}
		return flow;
	}
	int Maxflow(int s,int t){
		this->s=s;this->t=t;
		int flow=0;
		while(bfs()){
			memset(cur,0,sizeof(cur));flow+=dfs(s,INF);
		}
		return flow;
	}
};
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T;cin>>T;
	while(T--){
		Dicnic g;
		int n;cin>>n;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				int ok;cin>>ok;
				if(ok){
					g.addEdge(i,n+j,1);
				}
			}
		}
		//源点
		int s = 0,t=2*n+2;
		for(int i=1;i<=n;i++) g.addEdge(s,i,1);
		for(int i=1;i<=n;i++) g.addEdge(n+i,t,1);
		int flow = g.Maxflow(s,t);
		if(flow==n) cout<<"Yes\n";
		else cout<<"No\n";
	}
}

B. Fake Plastic Trees

链接
给一个树带点权.初始点权为0,希望每个树的点权介于区间 [ l v , r v ] [l_v,r_v] [lv,rv]
定义一个操作:对于点 v v v到根1路径上所有节点,增加一个值 c v c_v cv,但是要求从根到 v v v的值 c v c_v cv是一个非递减的数字.
求最少操作数使得每个节点的点权都处于它们对应的区间上.
思考:对于一个叶子结点,因为其区间至少是大于1的,意味着每个叶子都至少占用一次操作数,
既然希望操作数最少,而且叶子加完后,下一次选择的路径也不会包含它们,所以一种可能最优的情况是叶子直接取 r v r_v rv,对于它的父亲 u u u,可以选择的区间 [ 0 , m i n ( r u , r s o n ) ] [0,min(r_u,r_{son})] [0,min(ru,rson)],这暗示父亲在满足儿子节点的定义下,可以额外增加的值是可以计算出来的.
v a l u val_u valu = m i n ( r u , ∑ r s o n ) min(r_u,\sum{r_{son}}) min(ru,rson)
继续思考,既然儿子的值已经被计算出来了,父亲这个点也已经不再需要额外考虑了,索性能加就加,加的值取 v a l u val_u valu就行.
如果 v a l u > = l u val_u>=l_u valu>=lu,说明 u u u这个点已经不用继续考虑了,此时该点的取值就是 v a l u val_u valu
,否则,这个点就成为一个新的"叶子",下一次新增加的值希望最大,取 r u r_u ru

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e6+5;
const int INF = 1e9+7;
typedef long long ll;
typedef pair<int,int> pii;
#define all(a) (a).begin(), (a).end()
vector<int> G[maxn];
int l[maxn],r[maxn];
int dp[maxn];//the number of op of the subtree(u)
int add[maxn];//the number of u.
void init(int n){
	for(int i=0;i<=n;i++) G[i].clear();
	for(int i=0;i<=n;i++) dp[i]=add[i]=0;
}
void dfs(int u,int fa){
	int child = 0;
	int val = 0;
	for(auto v : G[u]){
		if(v==fa) continue;
		dfs(v,u);child++;
		val += add[v];
		dp[u]+=dp[v]; 
	}
	if(child==0) dp[u]=1,add[u]=r[u];
	else{
		val = min(r[u],val);
		if(val<l[u]) dp[u]+=1,add[u] = r[u];
		else add[u]=val;
	}
}
signed main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int T;cin>>T;
	while(T--){
		int n;cin>>n;
		init(n);
		for(int i=2;i<=n;i++){
			int p;cin>>p;
			G[i].push_back(p);
			G[p].push_back(i);
		}
		for(int i=1;i<=n;i++) cin>>l[i]>>r[i];
		dfs(1,0);
		cout<<dp[1]<<"\n";
	}
}

P1122 最大子树和

链接
求一个树的最大子树和.好像就是这样.dp乱搞一下先.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
const int INF = 1e9+7;
typedef long long ll;
typedef pair<int,int> pii;
#define all(a) (a).begin(), (a).end()
vector<ll> G[maxn];
ll val[maxn];ll dp[maxn];
void dfs(int u,int fa){
	dp[u] = val[u];
	for(auto v : G[u]){
		if(v==fa) continue;
		dfs(v,u);
		if(dp[v]>0) dp[u] += dp[v];
	}
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	ll n;cin>>n;
	ll ans = 1LL*INF*100000*-1;
	for(int i=1;i<=n;i++) cin>>val[i],ans=max(val[i],ans);
	for(int i=1;i<=n-1;i++){
		int u,v;cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	ans = max(ans,dp[1]);
//	for(auto v : G[1]){
//		ans = max(ans,dp[v]);
//	}
	cout<<ans<<"\n";
return 0;}

摸了,今天状态不佳

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

minato_yukina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值