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

題意概括:每個元素包含座標xx,地盤yy,收益pp[x,x+y][x,x+y]只能由一種元素收益

首先DP方程
f[i][1]f[i][1]表示ii
f[i][0]f[i][0]表示ii不選
f[i][1]f[i][1]要從所有a[j].x+y&gt;a[i].xf[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,da,b,c,d聯通最少需要幾條邊


nn遍最短路,四個點兩兩最短路,要麼路徑不相交,相交則去重

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章