1. 程式人生 > 資訊 >訊息稱特斯拉 Semi 電動半掛卡車可能使用 WAVE 無線充電系統

訊息稱特斯拉 Semi 電動半掛卡車可能使用 WAVE 無線充電系統

一、題目

點此看題

二、解法

答案上界顯然是 \(n\),我們考慮怎麼樣把答案變小,顯然我們要考慮怎麼合理利用操作二。

我們用圖論模型考慮操作的結構,如果對 \(u,v\) 使用了操作二,那麼我們把 \((u,v)\) 連邊。不難發現最優解的圖一定是操作二的一個森林,因為如果操作二成環那麼肯定沒有直接使用操作一要好。

那麼要求這個森林可以用狀壓 \(dp\) 的技巧解決,關鍵問題是如果判斷集合 \(s\) 是否能成為一棵樹。

這個問題比較複雜,可以考慮列舉法,假設我們已經枚舉出了樹的結構和每個點的點權:

如果我們不考慮那個 \(+1\),發現每個點的貢獻只和其深度有關,那麼我們最後列出來的方程就是:奇數位置的點權\(-\)

偶數位置的點權\(=0\),把 \(+1\) 考慮進去的話那麼每個 \(+1\) 可以任意貢獻 \(+1/-1\),所以可以列出方程:

\[|\sum_{i=1}^{sz}(-1)^{dep_i}\cdot v_i|<sz\ \ \ \ \sum_{i=1}^sv_i=sz\bmod 2 \]

暴力子集列舉是 \(O(3^n)\),可以考慮折半搜尋,也就是把兩邊的所有情況枚舉出來,時間複雜度 \(O(2^{\frac{|S|}{2}})\),然後 \(\tt two-pointers\) 合併即可,總時間複雜度 \(O((1+\sqrt 2)^n)\),證明:

\[\sum_{S\in U}2^{\frac{|S|}{2}}=\sum_{S\in U}\sqrt 2^{|S|}=\sum_{i=0}^n\sqrt 2^i\cdot{n\choose i}=(1+\sqrt 2)^n \]

最後考慮怎麼狀壓 \(dp\)

,好像還是要用子集列舉啊,但是這道題比較特殊可以加點剪枝,考慮刷表法,如果 \(f[s]\) 已經被組合出來過了就那它就不去更新,也就是我們只拿最基本的單位去更新,時間複雜度 \(O(3^n)\) 還是快啊。

三、總結

本題使用了推結論的兩大技巧:一是觀察操作結構然後轉圖論模型;而是列舉法確定狀態推出真正有關的量。我覺得列舉法是真的神奇,通過列舉我們可以知道所有資訊,然後分析列舉後的狀態看什麼資訊才是真正需要的。

子集列舉的優化技巧也值得借鑑,折半搜尋那個複雜度是真的牛逼,還可以考慮有效轉移來剪枝。

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
const int M = 1<<20;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,a[25],b[25],f[M],sl[M],sr[M];
int Abs(int x) {return x>0?x:-x;}
void get(int *s,int &k,int l,int r)
{
	s[0]=0;
	static int ad[M],sb[M];
	for(int i=l;i<r;i++,k<<=1)
	{
		for(int j=0;j<k;j++)
			ad[j]=s[j]+b[i],sb[j]=s[j]-b[i];
		int p=0,q=0,t=0;
		while(p<k && q<k) s[t]=ad[p]<sb[q]?ad[p++]:sb[q++],t++;
		while(p<k) s[t]=ad[p++],t++;
		while(q<k) s[t]=sb[q++],t++;
	}
}
int check(int s)
{
	int sz=0,sum=0,l=1,r=1;
	for(int i=0;i<n;i++)
		if(s&(1<<i)) b[sz++]=a[i],sum+=a[i];
	if(Abs(sum)%2!=(sz-1)%2) return 0;
	get(sl,l,0,sz/2);
	get(sr,r,sz/2,sz);
	int nd=1+(Abs(sum)<sz)*2;
	for(int i=r-1,j=0;i>=0;i--)
	{
		while(j<l && sl[j]+sr[i]<=-sz) j++;
		for(int k=j;k<l && nd && sl[k]+sr[i]<sz;k++)
			nd--;
	}
	return !nd;
}
signed main()
{
	n=read();
	for(int i=0;i<n;i++)
	{
		a[i]=read();
		if(!a[i]) {i--;n--;continue;}
	}
	m=(1<<n)-1;
	for(int s=1;s<=m;s++)
		if(!f[s] && check(s))
		{
			f[s]=1;int cs=m-s;
			for(int t=cs;t;t=(t-1)&cs)
				f[s|t]=max(f[s|t],f[s]+f[t]);
		}
	printf("%lld\n",n-f[m]);
}