2025 暑假集训 Day13

2025.8.18

比赛,预期 \(30+100+100+30=260\),实际 \(27+24+0+10\) 排名 \(28/40\) 太抽象了。T1 挂分的原因居然是数据范围的锅,数据范围写的是 \(30 \%\) 的数据 \(100,100\%\) 的数据 \(10^4\),但是实际上用 assert 试了一下,只有前三个点的数据是 \(\le 10000\) 的,其他的都大于 \(10000\),我数组大小只开了 \(10007\) 然后就爆炸了,喜提 RE27pts.

B. 灯

洛谷原题:P2962 [USACO09NOV] Lights G

首先可以发现这个开关灯的操作就相当于给灯的状态异或上 \(1\),然后就可以发现每个灯最多操作 \(1\) 次,所以我们不妨考虑枚举每一个灯开不开,时间复杂度 \(O(2^n)\) 显然会爆炸。

考虑使用 meet-in-the-middle 算法,将原来的枚举开关灯分成两半 \(1 \sim mid,mid+1 \sim n\),其中 \(mid=\left\lfloor \dfrac{(1+n)}{2} \right\rfloor\)。每一个灯的开关都可以用 1/0 表示,一共有 \(35\) 个灯,所以可以用一个 long long state 存下,其中 \(state\) 从低到高\(i\) 位为 \(1\) 表示第 \(i\) 个灯是开着的,否则是关着的。

对于前半部分,分别枚举第 \(i(1 \le i \le mid)\) 个灯要不要操作,然后搜索出“到达状态 \(state\) 需要的最少操作次数 \(dp_{state}\)”,对于后半部分首先分别枚举第 \(i(mid+1 \le i \le n)\) 要不要操作,得出一个状态 \(state\),然后找到与当前状态互补的状态(就是两个状态的按位异或 \(\operatorname {xor}\) 正好为所有灯都开着的状态 \(2^n-1\)),统计 \(dp_{(2^n-1) \operatorname {xor} state}\) 与后半部分所用操作数的次数的和,然后取一个最小值即为答案。

因为折半搜索之后只要记录操作第 \(1 \sim mid\) 个灯的状态,一共 \(2^{mid}\) 种,可以用一个 map 存下。还有要注意的是 1<< 要写成 1ll<<,因为会爆 int

#include<bits/stdc++.h>
#define popcnt(x) __builtin_popcount(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=40;
int n,m,ans=12180211;
vector<int> g[N];
map<ll,int> dp;  //dp[state]表示到达状态state需要的最少操作步骤
int main()
{
//	freopen("neuvillette.in","r",stdin);
//	freopen("neuvillette.out","w",stdout);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++)
	{
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	int mid=(1+n)>>1;
	//搜索1~mid的部分 
	for(ll i=0;i<(1ll<<mid);i++) //i二进制第j位为1表示要操作灯j
	{
		ll state=0;
		for(int j=1;j<=mid;j++)
		{
			if(i&(1ll<<(j-1)))
			{
				state^=1ll<<(j-1);
				for(int v:g[j]) state^=1ll<<(v-1);
			}
		}
		if(dp.find(state)==dp.end()) dp[state]=popcnt(i);
		else dp[state]=min(dp[state],popcnt(i));
	}
	//搜索mid+1~n的部分
	for(ll i=0;i<(1ll<<(n-mid));i++)
	{
		ll state=0;
		for(int j=1,jj=mid+1;j<=n-mid;j++,jj++)
		{
			if(i&(1ll<<(j-1)))
			{
				state^=1ll<<(jj-1);
				for(int v:g[jj]) state^=1ll<<(v-1);
			}
		}
		//寻找互补状态 
		if(dp.find(((1ll<<n)-1)^state)!=dp.end()) ans=min(ans,popcnt(i)+dp[((1ll<<n)-1)^state]);
	}
	cout<<ans;
	return 0;
}
/*
状态:state的第idx位为1表示这个灯是开着的,否则是关着的 
987654321 idx
101010001 state
*/
posted @ 2025-08-18 16:38  wwwidk1234  阅读(31)  评论(0)    收藏  举报