1. 程式人生 > 其它 >題解 星空

題解 星空

傳送門

這題什麼神仙轉化……
24pts狀壓賊好打考場上腦子抽了一直想特判
如果要在狀壓時同時找到兩個1轉移,第一層可以不列舉,而用第一個1代替,因為沒有第一個1的狀態也會被列舉到

「LATEX待補充」
想不到……就先順一遍題解思路吧
首先n有點大,而這裡需要區間修改
套個資料結構能維護修改
但是這只是不斷用區間修改嘗試統一整個區間的值考慮差分
類比藍書差分例題增減序列 ,只不過那道是求統一差分陣列的最小步數及方案數,這裡是求統一差分陣列的最小步數而已

然後轉化成了一個處理差分序列的問題
發現這裡\(k\)很小,差分序列裡只有不超過16個1,令其為\(cnt\)
再看所謂「翻轉」操作,在這裡即為選固定間距的點進行取反
取反兩個1相當於用一次操作將它們消掉
取反一個0和一個1相當於移動這個1
最後要把序列消成全0

\(2*k\leqslant16\)這個範圍適合暴力,考慮預處理通過移動消掉i,j的最少步數
這裡可以用最短路
然後列舉消點的過程可以狀壓
注意狀壓的時候可以用到一開始說的優化
如果要在狀壓時同時找到兩個1轉移,第一層可以不列舉,而用第一個1代替,因為沒有第一個1的狀態早晚被列舉到,就與這裡的轉移產生了重複
狀壓時間複雜度可以從\(O(k^2*2^{cnt})\)優化成\(O(k*2^{cnt})\)

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 40010
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, k, m;

namespace force{
	int dp[1<<16], s, cnt;
	bool vis[1<<16];
	queue<int> q;
	int len[N], b[N];
	void solve() {
		memset(dp, 127, sizeof(dp));
		s=(1<<n)-1;
		for (int i=1; i<=k; ++i) s^=(1<<(read()-1));
		for (int i=1; i<=m; ++i) {len[i]=read(); b[i]=(1<<len[i])-1;}
		q.push(s); dp[s]=0;
		while (!q.empty()) {
			//++cnt;
			s=q.front(); q.pop();
			vis[s]=0;
			for (int i=1,to; i<=m; ++i) 
				for (int j=n-len[i]; j>=0; --j) {
					to = s^(b[i]<<j);
					if (dp[s]+1<dp[to]) {
						dp[to]=dp[s]+1;
						if (!vis[to]) q.push(to);
					}
				}
		}
		printf("%d\n", dp[(1<<n)-1]);
		//cout<<"cnt: "<<cnt<<endl;
		//cout<<"2^n: "<<(1<<n)<<endl;
		exit(0);
	}
}

namespace task{
	int a[20], len[70], pos[20], cnt, cnt2;
	int dis[20][N], dp[1<<16];
	queue<int> q;
	bool vis[1<<16], point[N];
	void bfs(int s, int dis[]) {
		//cout<<"bfs "<<s<<endl;
		int cnt2=0;
		dis[s]=0; q.push(s);
		while (!q.empty()) {
			s=q.front(); q.pop();
			vis[s]=0;
			if (point[s] && ++cnt2>=cnt) {while (!q.empty()) {vis[q.front()]=0; q.pop();} return ;}
			for (int i=1,t; i<=m; ++i) {
				//cout<<"len: "<<len[i]<<endl;
				if ((t=s-len[i])>0 && dis[t]>dis[s]+1) {
					dis[t]=dis[s]+1;
					if (!vis[t]) q.push(t);
				}
				if ((t=s+len[i])<=n && dis[t]>dis[s]+1) {
					dis[t]=dis[s]+1;
					if (!vis[t]) q.push(t);
				}
			}
		}
		//cout<<"dis: "; for (int i=1; i<=n; ++i) cout<<dis[i]<<' '; cout<<endl;
	}
	void solve() {
		++n;
		memset(dis, 127, sizeof(dis));
		memset(dp, 127, sizeof(dp));
		for (int i=1; i<=k; ++i) a[i]=read();
		for (int i=1; i<=k; ++i) {
			if (i==1 || (i!=1&&a[i-1]!=a[i]-1)) pos[++cnt]=a[i], point[a[i]]=1;
			if (a[i+1]!=a[i]+1) pos[++cnt]=a[i]+1, point[a[i]+1]=1;
		}
		//cout<<"cnt: "<<cnt<<' '<<k*2<<endl;
		//cout<<"pos: "; for (int i=1; i<=cnt; ++i) cout<<pos[i]<<' '; cout<<endl;
		for (int i=1; i<=m; ++i) len[i]=read();
		for (int i=1; i<=cnt; ++i) bfs(pos[i], dis[i]);
		int s;
		dp[(1<<cnt)-1]=0; q.push((1<<cnt)-1);
		while (!q.empty()) {
			s=q.front(); q.pop();
			vis[s]=0;
			for (int i=0,t2,t3; i<cnt; ++i) if (s&(1<<i)) {
				for (int j=i+1; j<cnt; ++j) if (s&(1<<j)) {
					t2=s^(1<<i)^(1<<j); t3=dp[s]+dis[i+1][pos[j+1]];
					//cout<<"disk: "; for (int k=1; k<=n; ++k) cout<<dis[i+1][k]<<' '; cout<<endl; cout<<t3<<endl; cout<<pos[j+1]<<endl;
					if (dp[t2]>t3) {
						dp[t2]=t3;
						if (!vis[t2]) q.push(t2);
					}
				}
				break;
			}
		}
		printf("%d\n", dp[0]);
		//cout<<cnt2<<' '<<(1<<cnt)<<endl;
		exit(0);
	}
}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	
	n=read(); k=read(); m=read();
	task::solve();

	return 0;
}