• WC2022 Solution Set


    WC2022 Solution Set

    \(~~~~\) 蹭到了题单,所以随便挑点喜欢的题写写。

    Day1 直觉与证明

    \(~~~~\) 所以我也不知道从第三部分的几何开始它讲的与Topic有什么关系

    \(~~~~\) 所以说白了这个部分就是杂题选讲套了个直觉的壳子是吧

    Game Relics

    \(~~~~\)\(n\) 种物品,定向购买 \(i\) 的价格是 \(c_i\),也可以花 \(x\) 块钱从所有 \(n\) 种物品种随机一个。如果随机到一个 新物品,则直接得到;如果随机到一个已经有的 物品,则返还 \(\frac{x}{2}\) 块钱。
    \(~~~~\) 求最优策略下集齐所有物品至少要多少钱。

    \(~~~~\) \(1\leq n\leq 100,1\leq x\leq c_i\leq 10^4,\sum c_i\leq 10^4\).

    \(~~~~\) 从直觉来看,如果某一刻抽卡是最优策略,那么抽卡将会一直持续到抽出新东西为止。因为没抽出新东西时你并没有改变当前已有的东西,那么继续抽一定还是最优策略。抽卡停不下来的原因找到了!

    \(~~~~\) 因此,我们记当前有 \(i\) 个物品,那么期望还要 \(\dfrac{n}{n-i}\) 次抽出新东西,故抽卡抽出新东西的价格为 \((\dfrac{n}{n-i}-1)\times \dfrac{x}{2}+x=(\dfrac{n}{n-i}+1)\times \dfrac{x}{2}\)

    \(~~~~\) 注意到这个东西是随 \(i\) 单调递增的,直觉来看也就是越到后面越可能定向购买,所以最优策略是抽卡,然后开始买剩下的。

    \(~~~~\) 所以记 \(dp_{i,j}\) 表示,现在剩 \(i\) 张牌,其价格之和为 \(j\) 的概率,这可以通过组合数的递推转移。然后对每种情况,计算买来和抽来的平均价格更小者作为贡献即可。

    查看代码
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    #define db double
    db f[105][10005];
    int C[105],Sum;
    int main() {
    	int n;db x;
    	scanf("%d %lf",&n,&x);x/=2;
    	for(int i=1;i<=n;i++) scanf("%d",&C[i]),Sum+=C[i];
    	f[0][0]=1;
    	for(int i=1;i<=n;i++)
    		for(int j=i;j>=1;j--)
    			for(int k=C[i];k<=Sum;k++) f[j][k]=f[j][k]+f[j-1][k-C[i]]*j/(n-j+1);
    	db Ans=0;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=0;j<=Sum;j++) Ans+=f[i][j]*min((1.0*n/i+1)*x,1.0*j/i);
    	}
    	printf("%.12f",Ans);
    	return 0;
    }
    

    Swap Space

    Link

    \(~~~~\) 有若干个装满的容器 \(a\),每个容器需要进行升级改造,之后会获得新的容量 \(b\)(不一定会变大),为了使所有容器都被升级且所有东西都能被装下(不一定装回原容器),至少需要多少的额外容量。

    \(~~~~\) 直觉上应该先升级那些会变大的容器,那么这些容器内部的升级顺序呢?不难发现先升级 \(a\) 较小的,这样升级后多出来的空间说不定能顺便装下下一个容器里面的东西,不需要额外容量。同理,对于升级后会变小的容器,应该先升级 \(b\) 较大的,这样才能尽可能缓解先升级的容器对已经有的额外空间的占用。

    \(~~~~\) Sum-Up:关键在于对物品性质进行分类。

    查看代码
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    #define ll long long
    using namespace std;
    struct Drive{
    	int a,b;
    	Drive(){}
    	Drive(int A,int B){a=A,b=B;}
    }D1[1000005],D2[1000005];
    bool cmp1(Drive x,Drive y){return x.a<y.a;}
    bool cmp2(Drive x,Drive y){return x.b>y.b;} 
    int main() {
    	int n,cnt1=0,cnt2=0;
    	scanf("%d",&n);
    	for(int i=1,a,b;i<=n;i++)
    	{
    		scanf("%d %d",&a,&b);
    		if(a<=b) D1[++cnt1]=Drive(a,b);
    		else D2[++cnt2]=Drive(a,b); 
    	}
    	sort(D1+1,D1+1+cnt1,cmp1);
    	sort(D2+1,D2+1+cnt2,cmp2);
    	ll Ans=0,Rest=0;
    	for(int i=1;i<=cnt1;i++)
    	{
    		Rest-=D1[i].a;
    		if(Rest<0) Ans-=Rest,Rest=0;
    		Rest+=D1[i].b;
    	}
    	for(int i=1;i<=cnt2;i++)
    	{
    		Rest-=D2[i].a;
    		if(Rest<0) Ans-=Rest,Rest=0;
    		Rest+=D2[i].b;
    	}
    	printf("%lld",Ans);
    	return 0;
    }
    

    Is It Rated?

    Link

    \(~~~~\) 做判断题,你只知道每个人这道题的答案并被要求作答,作答完成后告诉你这道题的答案,要求错误次数不超过错得最少的人的 \(1.3\) 倍加 \(100\) 道。

    \(~~~~\) 直觉题 × 乱搞题 √

    \(~~~~\) 直觉上跟票当前错得最少的人即可。但如果有两个人都错得最少,且一道对一道错,你就会跟票导致50%的概率对一道。不太妙。

    \(~~~~\) 那我们加个权,对于错了 \(x\) 道题的人给他这次投的票加个权,考虑尽可能让错得少的人的权值更高,不妨用 \(a^x\)\(0<a\leq 1\)),然后再把权值对应成概率随机即可。

    \(~~~~\) 至于为什么它是对的,我!不!知!道!

    查看代码
    #include <bits/stdc++.h>
    using namespace std;
    double qpow(double a,int b)
    {
    	double ret=1;
    	while(b)
    	{
    		if(b&1) ret=ret*a;
    		b>>=1;a=a*a;
    	}
    	return ret;
    }
    int Wrong[100005];
    char Ans[5],Out[1005];
    int main() {
    	srand(time(NULL));
    	default_random_engine e(rand());
    	uniform_real_distribution<double> range(0,1);
    	int n,m;
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%s",Out+1);
    		double One=0,Zero=0;
    		for(int j=1;j<=n;j++)
    		{
    			if(Out[j]=='1') One+=qpow(0.7,Wrong[j]);
    			if(Out[j]=='0') Zero+=qpow(0.7,Wrong[j]);
    		}
    		printf("%d\n",range(e)<=One/(One+Zero));fflush(stdout);
    		scanf("%s",Ans+1);
    		for(int j=1;j<=n;j++) if(Out[j]!=Ans[1]) Wrong[j]++; 
    	}
    	return 0;
    }
    

    Date Pickup

    Link

    \(~~~~\) \(n\) 个点 \(m\) 条边的有向图,初始时在 \(1\) 号点,每一刻必须在图上走。在 \(a\sim b\) 分钟时会给出信号要求往 \(n\) 号结点走,求在任何时候从给出信号到到达 \(n\) 号结点的最小时间。

    \(~~~~\) 它与直觉和证明有什么关系呢?小编也不知道。

    \(~~~~\) 求出 \(1\) 号结点到所有点的最短路和所有结点到 \(n\) 号结点的最短路。

    \(~~~~\) 显然直接求解不好求,故考虑转化为判定问题:在 \(k\) 分钟内能不能到。

    \(~~~~\) 那么所有给信号的时候可以在的点 \(u\) 一定有 \(dis_{u,n}\leq k\)

    \(~~~~\) 然后,所有第一时间可以去的点应有 \(dis_{1,u}+dis_{u,n}\leq a+k\) ,同时,如果从这样的点向旁边走一个点仍能赶到,也就是 \(len_{u,v}+dis_{v,n}\leq k\) ,那么这样的边是可以走的,以此类推,我们可以得到一张可以游走的子图。

    \(~~~~\) 最后,我们只需要判断这张子图能不能让你撑到 \(b\) 时刻即可。具体来说,如果有环就在环里面绕,否则 \(\texttt{dp}\) 求最长路找到能不能撑这么久即可。

    查看代码
    #include <queue>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    #define ll long long
    #define PII pair<ll,ll>
    #define mp(a,b) make_pair(a,b)
    using namespace std;
    struct cmp{
    	bool operator()(const PII x,const PII y){return x.second>y.second;}
    };
    priority_queue<PII,vector<PII>,cmp>Q;
    ll a,b,n,m;
    bool vis[100005];
    ll dis[3][100005];
    vector < PII > G[3][100005];
    void Dij(ll op)
    {
    	op==1?dis[op][1]=0:dis[op][n]=0; memset(vis,0,sizeof(vis));
    	if(op==1) Q.push(mp(1,dis[op][1]));
    	else Q.push(mp(n,dis[op][n]));
    	while(!Q.empty())
    	{
    		ll u=Q.top().first;Q.pop();
    		if(vis[u]) continue; vis[u]=true;
    		for(ll i=0;i<G[op][u].size();i++)
    		{
    			ll v=G[op][u][i].first,w=G[op][u][i].second;
    			if(dis[op][v]>dis[op][u]+w)
    			{
    				dis[op][v]=dis[op][u]+w;
    				Q.push(mp(v,dis[op][v]));
    			}	
    		}
    	}
    }
    vector < PII > Sub[100005];
    bool InSub[100005];
    ll deg[100005];
    void BFS(ll Limit)
    {
    	queue<ll>q;
    	memset(deg,0,sizeof(deg)); memset(vis,0,sizeof(vis));
    	for(ll i=1;i<=n;i++)
    	{
    		Sub[i].clear();
    		if(InSub[i]&&dis[1][i]+dis[2][i]<=a+Limit) q.push(i),vis[i]=true;
    	}
    	while(!q.empty())
    	{
    		ll u=q.front();q.pop();
    		for(ll i=0;i<G[1][u].size();i++)
    		{
    			ll v=G[1][u][i].first,w=G[1][u][i].second;
    			if(w+dis[2][v]<=Limit)
    			{
    				if(!vis[v]) q.push(v),vis[v]=true;
    				Sub[u].push_back(G[1][u][i]);
    				deg[v]++;
    			}
    		}
    	}
    }
    ll dp[100005];
    bool Check(ll Limit)
    {
    	memset(dp,0xbf,sizeof(dp));
    	queue<ll>q;
    	for(ll i=1;i<=n;i++) if(vis[i]&&!deg[i]) q.push(i);
    	while(!q.empty())
    	{
    		ll u=q.front();q.pop();
    		if(dis[1][u]+dis[2][u]<=a+Limit) dp[u]=max(dp[u],a+Limit-dis[2][u]);
    		if(dp[u]>=b) return true;
    		for(ll i=0;i<Sub[u].size();i++)
    		{
    			ll v=Sub[u][i].first,w=Sub[u][i].second;
    			dp[v]=max(dp[v],dp[u]+w);
    			if(!(--deg[v])) q.push(v);
    		}
    	}
    	for(ll i=1;i<=n;i++) if(vis[i]&&deg[i]) return true;
    	return false;
    }
    bool check(ll k)
    {
    	memset(InSub,0,sizeof(InSub));
    	for(ll i=1;i<=n;i++) InSub[i]=(dis[2][i]<=k);
    	if(InSub[1]) return true;
    	BFS(k);
    	return Check(k);
    }
    int main() {
    	memset(dis,127,sizeof(dis));
    	scanf("%lld %lld %lld %lld",&a,&b,&n,&m);
    	for(ll i=1,u,v,w;i<=m;i++)
    	{
    		scanf("%lld %lld %lld",&u,&v,&w);
    		G[1][u].push_back(mp(v,w));
    		G[2][v].push_back(mp(u,w));
    	}
    	Dij(1); Dij(2);
    	ll l=0,r=dis[1][n],mid,Ans;
    	while(l<=r)
    	{
    		mid=(l+r)>>1;
    		if(check(mid)) r=mid-1,Ans=mid;
    		else l=mid+1;
    	}
    	printf("%lld",Ans);
    	return 0;
    }
    

    Day2 数据结构题目选讲

    新年的聚会

    \(~~~~\) 交互题。 有一张 \(n\) 个点 \(m\) 条边的图,你可以给定一个点集,询问该点集内的点之间有没有边。 你需要猜出这张图。

    \(~~~~\) \(1 \leq n \leq 1000, 1 \leq m \leq 2000\)。询问次数在 \(50000\) 次以内,点集大小和在 \(10^6\) 以内。

    \(~~~~\) 一个很神奇的的结论:对于有 \(m\) 条边的图,可以将它分为 \(\mathcal{O(\sqrt m)}\) 个独立集,至于为什么,我!不!知!道!

    \(~~~~\) 所以我们先问 \(\mathcal{O(n\sqrt{m})}\) 次找到所有独立集,然后对于两个独立集之间如果有边,就把较大的独立集裂开成一半分治找边。

    查看代码
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    #include "meeting.h"
    #define PII pair<int,int>
    #define mp(a,b) make_pair(a,b)
    using namespace std;
    bool meeting(vector<int> set);
    bool Check1(int p,vector <int> S)
    {
    	S.push_back(p);
    	return !meeting(S);
    }
    bool Check2(vector <int> S1,vector <int> S2)
    {
    	for(int i=0;i<S1.size();i++) S2.push_back(S1[i]);
    	return !meeting(S2);
    }
    int cnt;
    vector <PII> Ans; 
    vector <int> S[1005];
    void Solve(vector <int> A,vector <int> B)
    {
    	if(A.size()==1&&B.size()==1){Ans.push_back(mp(A[0],B[0]));return;}
    	if(A.size()<B.size()) swap(A,B);
    	vector <int> C,D;
    	for(int i=0;i<A.size()/2;i++) C.push_back(A[i]);
    	for(int i=A.size()/2;i<A.size();i++) D.push_back(A[i]);
    	if(Check2(C,B)) Solve(C,B);
    	if(Check2(D,B)) Solve(D,B);
    }
    vector<PII> solve(int n)
    {
    	for(int i=0;i<n;i++)
    	{
    		int Belong=0;
    		for(int j=1;j<=cnt;j++)
    		{
    			if(!Check1(i,S[j]))	{Belong=j;break;}
    		}
    		if(!Belong) S[++cnt].push_back(i);
    		else S[Belong].push_back(i);
    	}
    	for(int i=1;i<=cnt;i++)
    	{
    		for(int j=i+1;j<=cnt;j++)
    		{
    			if(Check2(S[i],S[j])) Solve(S[i],S[j]);
    		}
    	}
    	return Ans;
    }
    

    赶路

    \(~~~~\) 给平面上 \(n\) 个点,没有三点共线。 指定起点终点,找到一条不自交的路径。

    \(~~~~\) \(1\leq n\leq 500\).

    \(~~~~\) ¥老师一句话题解太难懂了吧。

    \(~~~~\) 定义 \(solve(s,t,S)\) 表示走集合 \(S\) 内的点,以 \(s\) 为起点,以 \(t\) 为终点。

    \(~~~~\) 不妨设 \(u\) 为一个中转点,那么相对 \(s\)\(u\) 来说在同一侧的点一定在 \(u\) 之前走,否则在 \(v\) 之后走。

    \(~~~~\) 于是,你做完了……

    \(~~~~\) 说不定跟¥老师的一样难懂

    查看代码
    #include <bits/stdc++.h>
    using namespace std;
    #define db double
    #define ll long long
    struct node{
    	int x,y;
    }P[505];
    vector<int> Ans;
    int check(int a,int b,int c)
    {
    	ll K=1ll*(P[b].x-P[a].x)*(P[c].y-P[a].y)-1ll*(P[c].x-P[a].x)*(P[b].y-P[a].y);
    	if(K>0) return 1;return 0;
    }
    void Solve(int s,int t,vector <int> V)
    {
    	if(V.empty()) return;
    	vector<int> a[2];
    	for(int i=1;i<V.size();i++) a[check(s,V[0],V[i])].push_back(V[i]);
    	int K=check(s,V[0],t);
    	Solve(s,V[0],a[K^1]),printf("%d ",V[0]),Solve(V[0],t,a[K]);
    }
    int main() {
    	int T,n;scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d",&n);
    		for(int i=1;i<=n;i++) scanf("%d %d",&P[i].x,&P[i].y);
    		Ans.clear();printf("1 ");
    		for(int i=2;i<n;i++) Ans.push_back(i);
    		Solve(1,n,Ans);printf("%d\n",n);
    	}
    	return 0;
    }
    

    Day 3杂题选讲

    \(~~~~\) 邓老师选择了弹幕最多的讲法。

    Od deski do deski

    \(~~~~\) Link

    \(~~~~\)\(n\) 棵树,每棵树有可能是 \(m\) 种之一。 小 C 每天可以选择连续的一段树砍掉:要求这一段的长度至少是 \(2\),并且第一棵与最后一棵种类相同。 问有多少种初始局面,使得存在一种方法通过若干天将树砍光。 对 \(10^9+7\) 取模。

    \(~~~~\) \(n \leq 3000, m \leq 10^9\).

    \(~~~~\) 考虑我们会不会判断一个序列有没有解,那肯定有 \(n^2\) 的dp:\(dp_i\) 表示前 \(i\) 个数是否合法,那么找一个位置 \(j\) ,其颜色和当前位置 \(i\) 一样,且 \(dp_{j-1}\)\(\text{true}.\)

    \(~~~~\) 换到这道题,那我们需要记一下当前位置是否合法,然后意识到转移与当前可放元素的大小有关,于是把大小放进dp里面,那就是正常的dp了。

    查看代码
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int f[3005][2];
    const int MOD=1e9+7;
    int main() {
    	int n,m;
    	scanf("%d %d",&n,&m);
    	f[0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		int Up=min(i,m);
    		for(int j=Up;j>=0;j--)
    		{
    			f[j+1][1]=(1ll*f[j+1][1]+1ll*f[j][0]*(m-j)%MOD)%MOD;
    			f[j][0]=(1ll*(1ll*f[j][0]+f[j][1])%MOD)*j%MOD;
    			f[j][1]=1ll*f[j][1]*(m-j)%MOD;
    		}
    	}
    	int Ans=0;
    	for(int i=0;i<=n;i++) Ans=(1ll*Ans+f[i][0])%MOD;
    	printf("%d",Ans);
    	return 0;
    }
    

    Robbery

    \(~~~~\)\(n\) 种物品,第 \(i\) 种质量为 \(i\),价格为 \(a_i\),每种物品的数量无限。 给定 \(k, w\),选择 \(k\) 个物品,满足质量总和为 \(w\),价格之和最大。

    \(~~~~\) \(n \leq 1000, k \leq 10^6, k \leq w \leq kn, 1 \leq a_i \leq 10^9\)

    \(~~~~\) 神仙题好吧。

    \(~~~~\)\(f(k,w)\) 表示选 \(k\) 件物品,质量为 \(w\) 时的最大花费,如果按普通转移显然会超,所以:

    \(~~~~\)\(k\) 为偶数时:\(f(k,w)=\max_{i=1}^n f(k-1,w-i)+a_i\)

    \(~~~~\) 否则:\(f(k,w)=\max_{|i|\leq \lfloor n/2 \rfloor } f(k/2,\lfloor w/2\rfloor-i)+f(k/2,\lceil w/2 \rceil+i)\)

    \(~~~~\) 至于为什么它是对的可以看官方题解:Link

    \(~~~~\) 这样做完过后复杂度降为 \(\mathcal{O(n^2\log n)}\) ,可以通过本题。

    查看代码
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define ll long long
    using namespace std;
    ll dp[1005][5000];
    int n,Num[1000005],val[100005],cnt,fix[1000005];
    void Divide(int k)
    {
    	if(k==0) return;
    	Num[k]=++cnt;
    	if(k&1) Divide(k-1);
    	else Divide(k/2);
    }
    ll Solve(ll k,ll w)
    {
    	if(w<0||(k&&w==0)) return -1e18;
    	if(!k)
    	{
    		if(w) return -1e18;
    		else return 0;
    	}
    	if(~dp[Num[k]][w-fix[k]+2500]) return dp[Num[k]][w-fix[k]+2500];
    	ll ret=-1e18;
    	if(k&1)
    	{
    		fix[k-1]=fix[k];
    		for(int i=1;i<=n;i++) ret=max(ret,Solve(k-1,w-i)+val[i]);	
    	}
    	else
    	{
    		fix[k/2]=fix[k]/2;
    		for(int i=-(n/2);i<=n/2;i++) ret=max(ret,Solve(k/2,(w/2)-i)+Solve(k/2,(w+1)/2+i));
    	}
    	return dp[Num[k]][w-fix[k]+2500]=ret;
    }
    int main() {
    	memset(dp,-1,sizeof(dp));
    	int k,w;
    	scanf("%d %d %d",&n,&k,&w);
    	Divide(k);
    	for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    	fix[k]=w;
    	printf("%lld",Solve(k,w));
    	return 0;
    }
    

    Random Pawn

    \(~~~~\) 圆上有 \(N\) 个点,你初始随机出生在一个点。第 \(i\) 号点有属性 \(A_i, B_i\)。如果你当前处在 \(i\) 号点,你可以选择结 束游戏并获得 \(A_i\) 元,或者花费 \(B_i\) 元将自己随机移动到左右两点中的一个。求最优策略的期望收益。收益为得到的钱减付出的钱。

    \(~~~~\) \(1\leq N\leq 2\times 10^5\)

    \(~~~~\) 考虑这题的弱化版 Balance Beam P ,即没有 \(B\) 的限制的情况,此时可以将最大值作为端点破环成链,放在两端。

    \(~~~~\) 这个时候可以发现值得结算的点一定是凸包上的点,对于不在凸包上的点则用其两边第一个凸包上的点来计算即可。

    \(~~~~\) 问题在于现在有了 \(b\) 的限制,我们可以发现 \(f_i=\max{A_i,\dfrac{1}{2}(f_{i-1}+f_{i+1})-B_i}\) ,那我们消去 \(B\) 的影响,令 \(g_i=f_i-C_i\) ,满足若不在凸包上的点(即不取自己作为收益的点) \(g_i=\dfrac{1}{2}(g_{i-1}+g_{i+1})\) ,把这个式子拆开,可以推出 \(g_i=\dfrac{1}{2}(f_{i-1}+f_{i+1})-B_i-C_i\) ,继续化:\(2(B_i+C_i)=f_{i-1}+f_{i+1}-2(f_i-C_i)=2C_i\) ,也就是说令 \(C_1=0,C_2=0\) ,那么现在就优势在我,套用前面那道弱化版即可。

    \(~~~~\) 但是需要注意单独算每个点的答案再加起来精度会爆炸,所以直接对凸包上的两个点算围成梯形面积,最后单独做除法。

    查看代码
    #include <cstdio>
    #include <algorithm>
    #define ll long long
    using namespace std;
    int n;
    int L[200005],R[200005];
    ll TmpA[200005],TmpB[200005],A[200005],B[200005],C[200005],Top,hep[200005];
    int main() {
    	scanf("%d",&n);
    	ll Maxn=0,pos=0;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&TmpA[i]);
    		Maxn=max(Maxn,TmpA[i]);
    		if(Maxn==TmpA[i]) pos=i;	
    	}
    	for(int i=1;i<=n;i++) scanf("%lld",&TmpB[i]);
    	int cnt=0;
    	for(int i=pos;i<=n;i++) A[cnt]=TmpA[i],B[cnt]=TmpB[i],cnt++;
    	for(int i=1;i<=pos;i++)  A[cnt]=TmpA[i],B[cnt]=TmpB[i],cnt++;
    	ll Ans=0;
    	for(int i=2;i<=n;i++) C[i]=(C[i-1]+B[i-1]<<1)-C[i-2],A[i]-=C[i],Ans+=(i==n)?0:(C[i]<<1);
    	for(int i=1;i<=n;i++)
    	{
    		while(Top&&(A[i]-A[hep[Top]])*(hep[Top]-hep[Top-1])>=(A[hep[Top]]-A[hep[Top-1]])*(i-hep[Top])) Top--;
    		hep[++Top]=i;
    	}
    	for(int i=0;i<Top;i++) Ans+=(hep[i+1]-hep[i]+1)*(A[hep[i]]+A[hep[i+1]])-(A[hep[i+1]]<<1);
    	
    	printf("%.12f",0.5*Ans/n);
    	return 0;
    }
    

    南京的构造

    \(~~~~\) Link

    \(~~~~\)\(n\) 盏灯排在圆周上。每次你可以选一盏没亮的灯,反转它和相邻两盏的状态。你需要在 \(2n\) 步之内点亮所有灯或者输出无解。

    \(~~~~\) 枚举第一个位置和第二个位置点了奇数次或者偶数次,可以顺次确定其他点(因为每个点只受三个点的影响)然后考虑如果一个位置要点,并且灯也是灭的,那么直接点这个点即可。

    \(~~~~\) 否则那就一定是这个灯是灭的,且这个点也不点,那它右边那个点必定要点,并且灯也应该是亮的(否则上一轮就被干掉了),所以再右边那个点也应该是要点且亮的。且最右边应该是暗的。

    0110
    

    \(~~~~\) 这种情况只需要依次点 \(1,2,1,3\) 四个灯即可,用四步解决了两个灯,所以最后最多 \(2n\)

    查看代码
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    char S[100005];
    int n,A[100005],B[100005];
    vector <int> Ans;
    bool Solve()
    {
    	for(int i=3;i<=n;i++) A[i]=B[i-1]^A[i-2]^A[i-1];
    	for(int i=1;i<=n;i++)
    	{
    		if(i==1&&((A[n]^A[1]^A[2])!=B[1])) return false;
    		else if(i==n&&((A[n]^A[n-1]^A[1])!=B[n])) return false;
    		else if(1<i&&i<n&&(A[i-1]^A[i]^A[i+1])!=B[i]) return false; 
    	}
    	Ans.clear();
    	for(int i=1;i<=n;i++) B[i]=1-B[i];
    	bool flag=true;
    	while(flag)
    	{
    		flag=false;
    		for(int i=1;i<=n;i++)
    		{
    			if(B[i]==0&&A[i]==1)
    			{
    				flag=true;
    				Ans.push_back(i),A[i]=0;
    				B[i]^=1; B[(i-1-1+n)%n+1]^=1; B[(i+1-1+n)%n+1]^=1;
    			}	
    		}	
    	}
    	flag=true;
    	while(flag)
    	{
    		flag=false;
    		for(int i=1;i<=n;i++)
    		{
    			int j=i%n+1,k=(i+1)%n+1;
    			if(A[i]==0&&A[j]==1&&A[k]==1&&B[i]==0&&B[j]==1&&B[k]==1)
    			{
    				flag=true;
    				Ans.push_back(i); Ans.push_back(j); Ans.push_back(i); Ans.push_back(k);
    				B[j]^=1; B[k]^=1; B[(k+1-1)%n+1]^=1;
    				A[j]^=1; A[k]^=1;
    			}
    		}	
    	}
    	printf("%d\n",(int)Ans.size());
    	for(int i=0;i<Ans.size();i++) printf("%d ",Ans[i]);
    	puts(""); return true;
    }
    int main() {
    	int T;
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d %s",&n,S+1);
    		for(int i=1;i<=n;i++) B[i]=1-(S[i]-'0');
    		bool flag=false;
    		if(!flag) memset(A,0,sizeof(A)),A[1]=0,A[2]=0,flag=Solve();
    		if(!flag) memset(A,0,sizeof(A)),A[1]=1,A[2]=0,flag=Solve(); 
    		if(!flag) memset(A,0,sizeof(A)),A[1]=0,A[2]=1,flag=Solve(); 
    		if(!flag) memset(A,0,sizeof(A)),A[1]=1,A[2]=1,flag=Solve();
    		if(!flag) puts("0");
    	}
    	return 0;
    }
    
  • 相关阅读:
    redis是什么?
    mysql用户权限设置
    大白话说Java反射:入门、使用、原理 (转)
    iptables只允许指定ip访问本机的指定端口
    CSS系列——前端进阶之路:初涉Less
    MySQL分页查询优化
    linux 面试题
    CSS中定义CLASS时,中间有空格和没空格的区别是什么?
    MySQL Explain详解
    EBS 系统当前完成请求时间监测
  • 原文地址:https://www.cnblogs.com/Azazel/p/15847211.html
Copyright © 2020-2023  润新知