20181031(DP二分优化+最短路乱搞+点分治(?))

地球发动机(earth)
【题目描述】
“啊,地球,我的流浪地球……”
——《流浪地球》
在一条直线上,从左到右排列着n台地球发动机,每台发动机有着固定的位置坐标Ai和功率Pi,保证Ai<Ai+1。此外,由于地球发动机的特性,每台发动机还有一个参数Xi,如果一台发动机运行,则坐标范围在[Ai,Ai+Xi]的其它发动机就无法运行。现在你想让正在运行的发动机总功率最大,请输出这个总功率。
【输入数据】
第一行一个整数n,意义如上所述。
接下来n行,每行三个整数Ai,Pi,Xi,意义如题面所述。
【输出数据】
一行一个整数,表示可能的最大功率。
【样例输入】
4
2 5 1
5 4 3
8 10 3
9 2 2
【样例输出】
15
【数据范围】
对于20%的数据,n≤10,0<Ai,Pi,Xi≤10;
对于50%的数据,n≤2000,0<Ai,Pi,Xi≤105;
对于100%的数据,n≤105,0<Ai,Pi,Xi≤109。

图(graph)
【题目描述】
小H有一张n个点,m条边的无向连通图,他想从图中选出一些边,保证通过这些边a和b连通,c和d连通,同时选出的边数尽量少。
【输入数据】
第一行两个整数n,m,表示图中的边数和点数。
第二行四个整数a,b,c,d,意义如题面所述。
接下来m行,每行两个整数a,b,表示点a和点b间有一条边。
【输出数据】
一行一个整数,表示最少需要选出的边数。
【样例输入】
5 8
3 4 1 3
2 1
3 2
4 3
5 3
4 2
1 4
5 4
2 1
【样例输出】
2
【数据范围】
对于所有数据,保证1≤a,b,c,d≤n;
对于10%的数据,0<n,m≤20;
对于30%的数据,0<n,m≤300;
对于60%的数据,0<n≤300;
对于100%的数据,0<n,m≤3000。

树(tree)
【题目描述】
小H有一棵n个节点的树T,每个节点上有一个非负整数Ai,他想知道所有距离不超过k的点对 ( x , y ) (x<y)上数的亦或值的和。
【输入数据】
第一行两个正整数n,k,表示树上的节点数和给定的距离k。
第二行n个非负整数,第i个数表示第i个节点上的数是Ai。
接下来n-1行,每行两个正整数u,v,表示节点u和节点v之间有一条边。保证输入是一棵树。
【输出数据】
一行一个整数,表示所有距离不超过k的点对上数的亦或值的和。
【样例输入】
6 3
7 4 4 3 2 0
2 1
6 2
5 6
3 5
4 1
【样例输出】
51

T1

题意概括:每个元素包含坐标 x x ,地盘 y y ,收益 p p [ x , x + y ] [x,x+y] 只能由一种元素收益

首先DP方程
f [ i ] [ 1 ] f[i][1] 表示 i i
f [ i ] [ 0 ] f[i][0] 表示 i i 不选
f [ i ] [ 1 ] f[i][1] 要从所有 a [ j ] . x + y &gt; a [ i ] . x f [ j ] [ 0 ] a[j].x+y&gt;a[i].x的f[j][0] 过继过来
f [ i ] [ 0 ] f[i][0] 要从之前所有的最大值过继过来

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
struct node
{
	int pos,id;
	long long data;
	node(int x,long long y):pos(x),data(y){}
	node(){}
	bool operator <(node x) const
	{
		return pos>x.pos;
	}
};
int n,a[100005],x[100005],h,t;
long long f[100005][2],ans,p[100005];
node q[100005];
priority_queue<node> o;
int main()
{
	cin>>n;
	for (int i=1;i<=n;i++) scanf("%d %lld %d",&a[i],&p[i],&x[i]);
	long long y=0;
	h=1;
	for (int i=1;i<=n;i++)
	{
		while (h<=t)
		{
			if (a[i]>q[h].pos)
			{
				y=max(y,q[h].data+p[q[h].id]);
				h++;
			}
			else break;
		}
		while (!o.empty())
		{
			if (a[i]>o.top().pos)
			{
				y=max(y,o.top().data);
				o.pop();
			}
			else break;
		}
		f[i][0]=max(q[h].data,y);
		f[i][1]=f[i][0]+p[i];
		while (a[i]+x[i]<q[t].pos) 
		{
			if (f[q[t].id][1]>y) o.push(node(q[t].pos,f[q[t].id][1]));
			t--;
		}
		t++;
		q[t].pos=a[i]+x[i];
		q[t].data=f[i][0];
		q[t].id=i;
		ans=max(ans,f[i][1]);
	}
	ans=max(ans,f[n][0]);
	cout<<ans;
}

T2

题意概括:一张图,使得 a , b , c , d a,b,c,d 联通最少需要几条边


n n 遍最短路,四个点两两最短路,要么路径不相交,相交则去重

#include<bits/stdc++.h>
using namespace std;
int n,m,u,v,b1[3010],b2[3010],b3[3010],b4[3010],d[3010],a,b,x,y,vi[3010];
int hed[3010],nex[6010],vt[6010],cnt,ans;
queue<int> id;

void add(int i,int j){
    vt[++cnt]=j,nex[cnt]=hed[i],hed[i]=cnt;
    vt[++cnt]=i,nex[cnt]=hed[j],hed[j]=cnt;
}

void bfs(int s,int *dd){
    memset(dd,0x3f,sizeof(dd));
    memset(vi,0,sizeof(vi));
    id.push(s),dd[s]=0,vi[s]=1;
    while(!id.empty()){
        u=id.front(),id.pop();
        for(int i=hed[u];i;i=nex[i]){
            if(vi[vt[i]]) continue;
            dd[vt[i]]=dd[u]+1,id.push(vt[i]),vi[vt[i]]=1;
        }
    }
}

int main(){
//  freopen("graph.in","r",stdin);
//  freopen("graph.out","w",stdout);
    scanf("%d%d%d%d%d%d",&n,&m,&a,&b,&x,&y);
    for(int i=1;i<=m;i++) scanf("%d%d",&u,&v),add(u,v);
    bfs(a,b1),bfs(b,b2),bfs(x,b3),bfs(y,b4);
    ans=b1[b]+b3[y];
    for(int i=1;i<=n;i++){
        bfs(i,d);
        for(int j=i;j<=n;j++){
            int tmp=min(b1[i]+b3[i]+b2[j]+b4[j]+d[j],b2[i]+b4[i]+b1[j]+b3[j]+d[j]);
            tmp=min(tmp,min(b1[i]+b4[i]+b2[j]+b3[j]+d[j],b1[j]+b4[j]+b2[i]+b3[i]+d[j]));
            ans=min(ans,tmp);
        }
    }
    printf("%d",ans);
}

T3

题意概括:RT
暴力的做法我就不献丑了
把题解拷上来
优化0:
首先每一个二进制位可以拆开来看,对答案没有影响。考虑一个树形DP,对每个二进制位分别统计对答案的影响。记f[x][i][j][0/1]为当前以x为根的子树中,深度为i的所有节点的第j位是0/1的数量。每将一个儿子y合并上去时,先利用前缀和统计两点分别在当前以x为根的子树和以y为根的子树中的答案,再将y的信息合并进x中。时间复杂度O(nklogA),期望得分40。
可以看出这个DP还可以继续优化。
优化1:使用点分治进行优化。每次合并时,用线段树统计答案,时间复杂度O(nlog2nlogA)。期望得分45-60。
优化2:继续使用点分治,观察到我们每次统计答案时都只会用线段树求一段前缀和,考虑改为使用前缀和,但发现这样合并一棵子树复杂度就不再是O(要合并的子树大小),而是O(整棵子树大小),会使复杂度爆炸,于是改为统计后缀和,这样总复杂度就降到了O(nlognlogA)。期望得分75-100。
优化3:观察原来的DP,发现第二维状态大小只和当前子树深度有关,于是使用长链剖分,依然沿用优化2中统计后缀和的方法合并子树信息和统计答案,时间复杂度降到了O(nlogA)。期望得分100。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read(){
	int f=1,g=0;char ch=getchar();
	for (;!isdigit(ch);ch=getchar()) if (ch=='-') f=-1;
   	for (;isdigit(ch);ch=getchar()) g=g*10+ch-'0';
   	return f*g;
}
const int N=500005;
int size,fir[N],n,k,depth[N],hs[N],cnt;
struct node{int f[20][2];}b[N],a[N],ta,tb;
node operator+(node a,int b){
	for (int i=0;i<20;i++)
	{a.f[i][b&1]++;b>>=1;}
	return a;
}
void operator+=(node &a,node b){
	for (int i=0;i<20;i++)
	{a.f[i][0]+=b.f[i][0];a.f[i][1]+=b.f[i][1];}
}
node operator+(node a,node b){a+=b;return a;}
node operator-(node a,node b){
	for (int i=0;i<20;i++)
	{a.f[i][0]-=b.f[i][0];a.f[i][1]-=b.f[i][1];}
	return a;
}
ll operator*(node &a,node &b){
	ll ans=0;
	for (int i=0;i<20;i++)
	ans+=((ll)a.f[i][0]*b.f[i][1]+(ll)a.f[i][1]*b.f[i][0])<<i;
	return ans;
}
node *f[N];
long long ans;
struct edge{int u,v,nex;}e[N*2];
void add(int u,int v){e[++size]=(edge){u,v,fir[u]};fir[u]=size;}
void clear(){
	memset(fir,0,sizeof(fir));
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(depth,0,sizeof(depth));
	memset(hs,0,sizeof(hs));
}
void build(int x,int fa){
	depth[x]=1;
	for (int i=fir[x];i;i=e[i].nex)
	if (e[i].v!=fa){
		int y=e[i].v;build(y,x);
		if (depth[y]+1>depth[x]){
			depth[x]=depth[y]+1;
			hs[x]=y;
		}
	}
}
void cre(int x){f[x]=b+cnt;cnt+=depth[x];}
node calc(int x,int i){
	if (i<0) return f[x][0];
	return (depth[x]<=i) ? b[0] : f[x][i];
}
node& calc(int x,int l,int r,node &a){return a=calc(x,l)-calc(x,r+1);}
void merge(int x,int y){
	for (int i=0;i<depth[y];i++)
	ans+=calc(y,i,i,ta)*calc(x,0,k-i-1,tb);
	for (int i=0;i<depth[y];i++)
	f[x][i+1]+=f[y][i];
	f[x][0]+=f[y][0];
}
void dp(int x,int fa){
	if (hs[x]){
		f[hs[x]]=f[x]+1;
		dp(hs[x],x);
		f[x][0]=f[x][1]+a[x];
		ans+=a[x]*calc(x,0,k,ta);
	}
	else f[x][0]=a[x];
	for (int i=fir[x];i;i=e[i].nex)
	if ((e[i].v!=fa)&&(e[i].v!=hs[x])){
		int y=e[i].v;cre(y);dp(y,x);
		merge(x,y);
	}
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n=read();k=read();
	size=ans=0;cnt=1;
	//clear();
	for (int i=1;i<=n;i++){
		int x=read();
		a[i]=a[i]+x;
	}
	for (int i=1;i<n;i++){
		int u=read(),v=read();
		add(u,v);add(v,u);
	}
	build(1,0);cre(1);
	dp(1,0);
	printf("%lld\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/beautiful_CXW/article/details/83582365
今日推荐