2019.08.24 日常总结

洛谷P2590:

题意:

一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。

我们将以下面的形式来要求你对这棵树完成一些操作:

I. CHANGE u t : 把结点u的权值改为t

II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值

III. QSUM u v: 询问从点u到点v的路径上的节点的权值和

注意:从点u到点v的路径上的节点包括u和v本身

思路:裸的树链剖分,这里我用了最常用的轻重链剖分

轻重链剖分:

我们记sze[i]为以i为根的树的节点个数,v为i的儿子,那么sze[v]最大的v,我们称为重儿子,从i到重儿子的边叫做重边,其余叫轻边,把所有的重边剖分为一条链,那么整棵树被我们剖分成了好几条链,对于每条链,我们都用线段树去维护,这样就把树的操作转化为了对好几条链的操作

特点:思路好想,但代码沉长,容易错

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=30100;
struct node{
	ll next,to;
}e[N<<1];ll h[N],tot;
inline void add(ll a,ll b){
	e[++tot]=(node){h[a],b};h[a]=tot;
	e[++tot]=(node){h[b],a};h[b]=tot;
}
ll sze[N],son[N],top[N],dfscnt;
ll dep[N],rev[N],seg[N],fa[N];
//fa[i]表示i的父亲,sze[i]表示以i为根的子树大小
//dep[i]表示i的深度,top[i]表示i所在链的开头
//son[i]表示i的重儿子,seg[i]表示i的dfs序
//rev[i]表示dfs序为i的节点,即rev[seg[i]]=i
void dfs1(ll u,ll f){
	dep[u]=dep[f]+1;
	fa[u]=f;sze[u]=1;
	ll maxsize=-1,heavyson=0;
	for(ll i=h[u];i;i=e[i].next){
		register ll v=e[i].to;
		if (v==f) continue;dfs1(v,u);
		if (sze[v]>maxsize){
			maxsize=sze[v];
			heavyson=v;
		}
		sze[u]+=sze[v];
	}
	son[u]=heavyson;
}
void dfs2(ll u,ll Top){
	if (son[u]){
		seg[son[u]]=++dfscnt;
		rev[dfscnt]=son[u];
		top[son[u]]=Top;
		dfs2(son[u],Top);
	}
	else return;
	for(ll i=h[u];i;i=e[i].next){
		register ll v=e[i].to;
		if (v!=fa[u]&&v!=son[u]){
			seg[v]=++dfscnt;
			rev[dfscnt]=v;
			top[v]=v;dfs2(v,v);
		}
	}
}
ll sum[N<<2],Max[N<<2],a[N];
//这三个数组都是线段树用的
inline void pushup(ll o){
	sum[o]=sum[o<<1]+sum[o<<1|1];
	Max[o]=max(Max[o<<1],Max[o<<1|1]);
}
void build(ll o,ll l,ll r){
	if (l==r){
		sum[o]=a[rev[l]];
		Max[o]=a[rev[r]];
		return;
	}
	ll mid=(l+r)>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	pushup(o);return;
}
void update(ll o,ll l,ll r,ll p,ll v){
	if (l==r&&r==p){
		sum[o]=Max[o]=v;
		return;
	}
	if (l>p||r<p) return;
	register ll mid=(l+r)>>1;
	if (p<=mid) update(o<<1,l,mid,p,v);
	if (mid<p) update(o<<1|1,mid+1,r,p,v);
	pushup(o);return;
}
ll querymax(ll o,ll l,ll r,ll p,ll q){
//	printf("querymax(%d,%d,%d,%d,%d)\n",o,l,r,p,q);
	if (p<=l&&r<=q) return Max[o];
	if (l>q||r<p) return -1000;
	register ll ans=-1000,mid=(l+r)>>1;
	if (p<=mid) ans=max(ans,querymax(o<<1,l,mid,p,q));
	if (mid<q) ans=max(ans,querymax(o<<1|1,mid+1,r,p,q));
	return ans;
}
ll querysum(ll o,ll l,ll r,ll p,ll q){
	if (p<=l&&r<=q) return sum[o];
	if (l>q||r<p) return 0;
	register ll ans=0,mid=(l+r)>>1;
	if (p<=mid) ans+=querysum(o<<1,l,mid,p,q);
	if (mid<q) ans+=querysum(o<<1|1,mid+1,r,p,q);
	return ans;
}
#define gc getchar()
#define g(c) isdigit(c)
inline ll read(){
	ll x=0;char c=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
ll n,m,i,x,y;string s;
inline ll QMAX(ll x,ll y){
	register ll ans=-1000;
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=max(ans,querymax(1,1,n,seg[top[x]],seg[x]));
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	return max(ans,querymax(1,1,n,seg[x],seg[y]));
}
inline ll QSUM(ll x,ll y){
	register ll ans=0;
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=querysum(1,1,n,seg[top[x]],seg[x]);
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	return ans+querysum(1,1,n,seg[x],seg[y]);
}
int main(){
	freopen("t1.in","r",stdin);
	n=read();
	for(i=1;i<n;i++)
	add(read(),read());
//	printf("1\n");
	for(i=1;i<=n;i++)
	a[i]=read();
	seg[1]=rev[1]=1;
	top[1]=dfscnt=1;
//	printf("2\n");
	dfs1(1,0);//printf("3\n");
	dfs2(1,1);//printf("4\n");
//	for(i=1;i<=n;i++){
//		printf("top[%d]=%d\n",i,top[i]);
//	}
	build(1,1,n);m=read();
//	printf("5\n");
	for(i=1;i<=m;i++){
		cin>>s;x=read();y=read();
//		cout<<s<<" "<<x<<" "<<y<<endl;
		if (s.find('X')!=s.npos){
			if (x>y) swap(x,y);
			printf("%d\n",QMAX(x,y));
		}
		else if (s.find('S')!=s.npos){
			if (x>y) swap(x,y);
			printf("%d\n",QSUM(x,y));
		}
		else update(1,1,n,seg[x],y);
//		printf("-----i=%d-----\n",i);
	}
	return 0;
}

洛谷P1247:

题意:

输入k及k个整数n1,n2,…,nk,表示有k堆火柴棒,第i堆火柴棒的根数为ni;接着便是你和计算机取火柴棒的对弈游戏。取的规则如下:每次可以从一堆中取走若干根火柴,也可以一堆全部取走,但不允许跨堆取,也不允许不取。

谁取走最后一根火柴为胜利者。

编一个程序,在给出初始状态之后,判断是先取必胜还是先取必败,如果是先取必胜,请输出第一次该如何取。如果是先取必败,则输出“lose”。

思路:很经典的博弈论的题目,先手必胜当且仅当n[1]^n[2]^n[3]^...^n[k]\neq 0,其中^表示异或,然后当n[i]^(n[1]^n[2]^n[3]^...^n[k])<n[i]时,我们就在n[i]中取n[i]-(n[i]^(n[1]^n[2]^n[3]^...^n[k]))个石子即可

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+3e2;
int a[N],ans,i,j,n;
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
//数据量太大,打个快读
bool first=true;
int main(){
	freopen("t1.in","r",stdin);
	n=read();
	for(i=1;i<=n;i++){
		a[i]=read();
		ans^=a[i];
	}
	if (ans==0) printf("lose");//先手必败
	else{
		for(i=1;i<=n;i++)
		if ((ans^a[i])<a[i]){
			int p=ans^a[i];
			printf("%d %d\n",a[i]-p,i);
			for(j=1;j<=n;j++){
				if (first) first=false;
				else printf(" ");
				if (j==i) printf("%d",p);
				else printf("%d",a[j]);
			}
			return 0;//这个return 0很重要,否则你的评测将五彩缤纷!
		}
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值