【训练小结】Petrozavodsk Summer-2014. Warsaw U Contest

trac

题解

D

题意:
给你一个连通的无向图,有偶数条边。n,m≤2e5, 现在要求你给每一对奇度点找到一条路径,使得
这些路径不共用边,而且自身也是简单路。
每条路径长度为偶数。

题解:
在dfs树上把相邻边两两配对。因为一定有偶数条边且图联通,一定存在这样的匹配。
这样匹配后有一个非常好的性质:
所有度数为偶数的点一定作为偶数次端点,而度数为奇数的点作为奇数次。
即按照匹配边连边,原图中的偶度点度数一定度数仍为偶数,奇度点一定度数仍为奇数。
考虑一条路径,中间的点度数一定为2(减2),两端的点度数一定为1(减一)
那么,我们从任意奇数度的点开始走,暴力走到不能走为止,停止的点一定是奇度的点。
并且一个还未作为终点的奇度点一定至少有一条出边,这就保证了一定有解!

总结:
这道题真的很巧!
要求路径长度为偶数,就一次走两条边,把边两两匹配。
利用删去一条路径,路径中的点奇偶性不变,直接构造解。
图论题要大胆猜结论,利用反正和性质不变性证明!
偶数条边边匹配一定存在也是一个很经典的性质

由于看了标程,代码基本和std一模一样。
注意匹配的时候返祖边有方向,只能匹配一次。

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
#define getL(x) (((x) - 1) * nsz + 1)
#define getR(x) (min(n,(x) * nsz))
#define ceil(x,y) ((x) + (y) - 1) / (y)

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;

const int inf = 1e5;
const int N = 3e6 + 10;
const int maxn = 500020;
const int BLK = 520;

vector <pr> t[maxn];
vector <pair<int,pr> > g[maxn];
int tag[maxn],vis[maxn],cnt,edge[maxn][3];
int n,m,dfstime;

void match(int a,int b,int e1,int e2){
//	cout<<e1<<" "<<e2<<endl;
	++cnt;
	//edge[cnt][0] = a + b;
	edge[cnt][1] = e1;
	edge[cnt][2] = e2;
	g[a].pb(mp(b,mp(cnt,0)));
	g[b].pb(mp(a,mp(cnt,1)));
}
int dfs(int x,int fa,int fa_e){
	int p_e = -1 , p_k = -1;
	vis[x] = ++dfstime;
	rvc(i,t[x]){
		int to = t[x][i].fi;
		int c_e = t[x][i].se;
		if ( to == fa ) continue;
		if ( (vis[to] && vis[to] < vis[x]) || (!vis[to] && dfs(to,x,c_e)) ){
			if ( p_e == -1 ){
				p_e = c_e;
				p_k = to;
			}
			else{
				match(p_k,to,p_e,c_e);
				p_e = -1;
			}
		}
	}
	if ( p_e != -1 ){
		match(p_k,fa,p_e,fa_e);
		return 0;
	}
	return 1;
}
int main(){
	scanf("%d %d",&n,&m);
	rep(i,1,m){
		int x,y;
		scanf("%d %d",&x,&y);
		t[x].pb(mp(y,i));
		t[y].pb(mp(x,i));
	}
	dfs(1,-1,-1);
	rep(i,1,n) vis[i] = 0;
	rep(i,1,n){
		if ( !vis[i] && (t[i].size() & 1) ){
			vector <int> ans(0);
			vis[i] = 1;
			int x = i;
			//cout<<"check "<<i<<endl;
			while ( 1 ){
				if ( !g[x].size() ){
					vis[x] = 1;
					break;
				}
				while ( g[x].size() ){ //找一条未被使用的匹配边,走过这两条边
					int to = g[x].back().fi;
					int c = g[x].back().se.fi;
					int rev = g[x].back().se.se;
					g[x].pop_back();
					if ( tag[c] ) continue;
					tag[c] = 1;
					int e1 = edge[c][1] , e2 = edge[c][2];
					if ( rev ) swap(e1,e2);
					ans.pb(e1) , ans.pb(e2);
					x = to;
				//	cout<<x<<endl;
					break;
				}
				//cout<<endl;
			}
			printf("%d %d %d\n",i,x,ans.size());
			rvc(j,ans){
				printf("%d ",ans[j]);
			}
			puts("");
		}
	}
}
H

题意:
平面上有n≤2e5个宝藏,每个有个价值vi。有m≤2e5个警卫,每个警卫有个位置,面朝y轴负方向,有个视野。贿赂每个警卫需要代价bi。你能得到宝藏,只有把看护宝藏的警卫全部贿赂了。问你净收益最大是多少。

做法:from new-meta
最小割。考虑所有的宝藏先都拿了,然后要么放弃一个宝藏,要么把看护宝藏的警卫都贿赂了。
最小割转成最大流。考虑每个警卫有一些流,可以给那些宝藏。把警卫的视野拉伸转到右下角的话,发现越低的宝藏能流给他的警卫越多。
考虑贪心,按x从大到小排序。然后每来一个警卫,尽量留给那些y较大的宝藏。直到自己没有流了,或宝藏都流满了了。
复杂度O((m+n)log(m+n))。

直接利用性质模拟最大流(最小割)
每次贪心的增广,因为是矩形,按x坐标排序后从大到小加入,每个守卫一定用y坐标最小的增广流量更优。相当于不退流的dinic
一开始想到线段树优化建边,但是显然不可能承受如此高的复杂度。
因为矩形有优美的性质,所以可以贪心的增广。积累这个思路!
另外,这道题应该有DP的做法。@lyk。待更新

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
#define getL(x) (((x) - 1) * nsz + 1)
#define getR(x) (min(n,(x) * nsz))
#define ceil(x,y) ((x) + (y) - 1) / (y)

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;

const int inf = 1e5;
const int N = 3e6 + 10;
const int maxn = 500020;
const int BLK = 520;

struct node{
	ll x,y,b;
	int idx,idy,tp,val;
	bool operator < (node a)const{
		if ( idx == a.idx ){
			if ( idy == a.idy ) return tp < a.tp;
			return idy > a.idy;
		}
		return idx > a.idx;
	}
}dt[maxn],dt2[maxn];
int w,h,n,m;
int tot;
ll b[maxn];
set <pr> s;

bool cmp(ll x,ll y){ return x > y; }
void pre(){
	//处理x坐标
	rep(i,1,n){
		dt[i].b = -(dt[i].y * w - dt[i].x * h);
		b[++tot] = dt[i].b;
	}
	rep(i,1,m){
		dt2[i].b = -(dt2[i].y * w - dt2[i].x * h);
		b[++tot] = dt2[i].b;
	}
	sort(b + 1,b + tot + 1);
	rep(i,1,n){
		dt[i].idx = lower_bound(b + 1,b + tot + 1,dt[i].b) - b;
	}
	rep(i,1,m){
		dt2[i].idx = lower_bound(b + 1,b + tot + 1,dt2[i].b) - b;
	}
	//处理y坐标
	tot = 0;
	rep(i,1,n){
		dt[i].b = -(dt[i].y * w + dt[i].x * h);
		b[++tot] = dt[i].b;
	}
	rep(i,1,m){
		dt2[i].b = -(dt2[i].y * w + dt2[i].x * h);
		b[++tot] = dt[i].b;
	}
	sort(b + 1,b + tot + 1);
	rep(i,1,n){
		dt[i].idy = lower_bound(b + 1,b + tot + 1,dt[i].b) - b;
	}
	rep(i,1,m){
		dt2[i].idy = lower_bound(b + 1,b + tot + 1,dt2[i].b) - b;
	}
	tot = n;
	rep(i,1,m) dt[++tot] = dt2[i] , dt[tot].tp = 1;
	sort(dt + 1,dt + tot + 1);
//	rep(i,1,tot){
//		cout<<dt[i].idx<<" "<<dt[i].idy<<" "<<dt[i].tp<<" "<<dt[i].val<<endl;
	//}
}
void solve(){
	ll ans = 0;
	rep(i,1,tot){
		if ( !dt[i].tp ){
			s.insert(mp(dt[i].idy,dt[i].val));
			ans += dt[i].val;
		}
		else{
			int delta = dt[i].val , y = dt[i].idy;
			while ( 1 ){
				auto it = s.lower_bound(mp(y,0));
				if ( it == s.end() ) break;
				pr cur = *it; s.erase(it);
				int d = min(delta,cur.se);
				delta -= d , ans -= d , cur.se -= d;
				if ( cur.se ) s.insert(cur);
				if ( !delta ) break;
			}
		}
	}
	cout<<ans<<endl;
}
int main(){
	scanf("%d %d %d %d",&n,&m,&w,&h);
	for (int i = 1 ; i <= n ; i++){
		scanf("%lld %lld %d",&dt[i].x,&dt[i].y,&dt[i].val);
	}
	for (int i = 1 ; i <= m ; i++){
		scanf("%lld %lld %d",&dt2[i].x,&dt2[i].y,&dt2[i].val);
	}
	pre();
	solve();
}

K

题意
现在给你n≤1e6个人,你要求把这些人分成尽可能多的连续段,其中每个人都会有要求,要求自己所在的段的人数在[ci,di]内。现在问最后最多能分成多少段,还有方案数。
5s

题解
这是经典题啊,开始觉得这道题很神,没有找到方向,后来看到没人过就仍了,现在发现其实列出DP方程以后很显然啊
f[i] = max(f[j] + 1) , j 可以转移到i , 然而这个东西不连续。
那么我们用cdq分治来维护这个转移(真的是常见套路啊!)
分治过程中我们考虑左边的点对右边的贡献区间,即把这个点的答案挂在区间的左端点(插入),右端点(删除)
在右边我们知道它可以接受的贡献区间,
即区间查询即可
维护方案数就是维护一个贡献二元组,代码上本质没有区间,只是常数增加
这里注意细节!当二元组的方案数=0,表示状态不合法,不能用它来更新。
然后O(nlog^2)有些卡常,不过这个贡献其实不满的,要把不合法的情况剪枝。
在用一个zkw线段树,本机1s轻松过掉。
还有就是vector要即时释放空间,不然会MLE

还是调了40min,因为:当二元组的方案数=0,表示状态不合法,不能用它来更新。没有考虑到。
思维不够全面啊!
这种题赛场上就应该想到,提高自己的思维能力,模型要多总结,转化的时候要从多个角度想

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
#define getL(x) (((x) - 1) * nsz + 1)
#define getR(x) (min(n,(x) * nsz))
#define ceil(x,y) ((x) + (y) - 1) / (y)

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;

const int inf = 1e7;
const int N = 3e6 + 10;
const int maxn = 1000020;
const int BLK = 520;
const int mod = 1e9 + 7;

inline void up(int &x,int y){
	x += y;
	if ( x >= mod ) x -= mod;
}
struct data{
	int num,mx;
	data () { num = mx = 0; }
	data (int x,int y):num(x),mx(y){}
	data operator + (data a){
		data cur;
		if ( mx == a.mx ){
			up(cur.num,num + a.num);
			cur.mx = mx;
		}
		else if ( mx < a.mx && a.num ) cur = a;
		else cur = data(num,mx);
		return cur;
	}
};
int c[maxn],d[maxn],n;
data f[maxn];
vector <pr> vec[maxn];

namespace Seg{
	data z[(1 << 21) + 20];
	int M;
	void build(int n){
		for (M = 1 ; M <= n + 1 ; M <<= 1);
		for (int i = 1 ; i <= M + n + 1 ; i++) z[i] = data(0,0);
	}
	inline void remove(int id){
		id += M;
		z[id] = data(0,0);
		while ( id > 1 ){
			z[id >> 1] = z[id] + z[id ^ 1];
			id >>= 1;
		}
	}
	inline void insert(int id,data c){
		if ( !c.num ) return;
		id += M;
		z[id] = z[id] + c;
		while ( id > 1 ){
			z[id >> 1] = z[id] + z[id ^ 1];
			id >>= 1;
		}
	}
	data query(int l,int r){
		//cout<<l<<" "<<r<<endl;
		//l-- , r++;
		data res;
		for (l = l + M - 1 , r = r + M + 1 ; l ^ r ^ 1 ; l >>= 1 , r >>= 1){
			if ( ~l & 1 ) res = res + z[l ^ 1];
			if ( r & 1 ) res = res + z[r ^ 1];
		}
		return res;
	}
}
using namespace Seg;


void ClearVector( vector< pr >& vt ) 
{
	vt.resize(0);
    vector< pr > veTemp; 
    veTemp.swap( vt );
}
void solve(int l,int r){
	if ( l == r ){
		if ( c[l] == 1 ) f[l] = f[l] + data(f[l - 1].num,f[l - 1].mx + 1);
		return;
	}
	int mid = (l + r) >> 1;
	solve(l,mid);
	//考虑左边对右边的贡献
	int low = 0 , high = inf;
	//cout<<"check : "<<l<<" "<<r<<endl;
	repd(i,mid,l){
		low = max(low,c[i]);
		high = min(high,d[i]);
		if ( low > high || high < mid - i + 2 ) break; 
		if ( low > r - i + 1 ) continue;
		int curl = max(mid + 1,i + low - 1) , curr = min(r,i + high - 1);
		vec[curl].pb(mp(i,1));
		vec[curr + 1].pb(mp(i,-1));
	//	cout<<i<<" "<<curl<<" "<<curr<<endl;
	}
	low = 0 , high = inf;
	build(mid - l + 1);
	rep(i,mid + 1,r){
		low = max(low,c[i]);
		high = min(high,d[i]);
		if ( low > high || high < i - mid + 1 ) break;
	//	cout<<"check : "<<i<<endl;
		rvc(j,vec[i]){
			int id = vec[i][j].fi - 1 , tp = vec[i][j].se;
			if ( tp == 1 ){
			 	insert(id - (l - 1) + 1,data(f[id].num,f[id].mx + 1));
			}
			else remove(id - (l - 1) + 1);
			//cout<<id<<" "<<f[id].num<<" "<<f[id].mx<<" "<<tp<<endl;

		//	cout<<id<<" "<<tp<<endl;
		}
		if ( low > i - l + 1 ) continue;
		int curl = max(l,i - high + 1) , curr = min(mid,i - low + 1);
	//	cout<<"query : "<<i<<" "<<curl<<" "<<curr<<endl;
		f[i] = f[i] + query(curl - (l - 1),curr - (l - 1));
	}
	rep(i,mid + 1,r + 1) ClearVector(vec[i]);
	solve(mid + 1,r);
}
int main(){
	///freopen("input.txt","r",stdin);
	scanf("%d",&n);
	rep(i,1,n) scanf("%d %d",&c[i],&d[i]);
	f[0] = data(1,0);
	solve(1,n);
	//rep(i,1,n){
	//	cout<<f[i].mx<<" "<<f[i].num<<endl;
	//}
	if ( f[n].num ) printf("%d %d\n",f[n].mx,f[n].num);
	else puts("NIE");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值