1. 程式人生 > >[USACO12OPEN]平衡的奶牛群,洛谷P3067,Meet in the Middle + Two-Pointers

[USACO12OPEN]平衡的奶牛群,洛谷P3067,Meet in the Middle + Two-Pointers

正題

      [USACO12OPEN]平衡的奶牛群

      題目很明瞭。

      看到n才20.

      暴力,列舉一個點在左邊,在右邊還是不放。記錄一下每個狀態是否被記錄過(放左邊和放右邊都是放,所以要開一個state來記錄這個取和不取的狀態是否被記錄過

      發現是3^n,好大啊。

      優化搜尋想到Meet in the Middle。

      接著,我們就可以處理出左邊的狀態和右邊的狀態。

      這個狀態是指什麼呢?

      指的是選出的放右邊的減去選出的放左邊的差。

      那麼兩邊的狀態如果存在a和b,並且a+b=0。這一組就是答案,最後減去1即可。空集不算。

      問題就在於怎麼尋找a+b=0的狀態個數。

      首先我們可以對左邊的狀態排一次序,然後用右邊的b來找一個左邊的a,使得a=-b。二分查詢可以完成。

      但是有更快的方法。

      把a從小到大排序,把b從大到小排序,那麼a遞增,b也單調遞減,所以就直接上Two-Pointers。

      注意兩個a相同要重新算一遍。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n;
int s[25];
struct node{
	int x,s;
}a[60010],b[60010];
bool tf[1050000];

bool cmp1(node x,node y){
	return x.x<y.x;
}

bool cmp2(node x,node y){
	return x.x>y.x;
}

void dfs(int x,int y,int l,int r,int sta,node*now){
	if(x>y){
		now[++now[0].x].x=r-l;
		now[now[0].x].s=sta;
		return ;
	}
	dfs(x+1,y,l,r,sta,now);
	dfs(x+1,y,l+s[x],r,sta|(1<<(x-1)),now);
	dfs(x+1,y,l,r+s[x],sta|(1<<(x-1)),now);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&s[i]);
	int mid=(1+n)/2;
	dfs(1,mid,0,0,0,a);
	dfs(mid+1,n,0,0,0,b);
	sort(a+1,a+1+a[0].x,cmp1);
	sort(b+1,b+1+b[0].x,cmp2);
	int ans=0;
	int l=1,r=1;
	while(l<=a[0].x && r<=b[0].x){
		int pos=r;
		while(r<=b[0].x && a[l].x+b[r].x>=0){
			if(a[l].x+b[r].x==0 && !tf[a[l].s|b[r].s]) ans++,tf[a[l].s|b[r].s]=true;
			r++;
		}
		if(l!=a[0].x && a[l+1].x==a[l].x) r=pos;
		l++;
	}
	printf("%d\n",ans-1);
}