UOJ354 新年的投票

小山云盘發表於2024-08-10

最近我部落格似乎出了點 bug,倒是不太會修,將就著看吧。

本文主要關注第四個子任務,前面三個有敘述不清的敬請諒解。

UOJ354 新年的投票

Sub1

不管人的編號直接爆搜就能夠找到一個合法方案。

Sub3

\(i\) 個人假設自己是第一個 \(1\)\(1\sim i-1\) 的都不能是 \(1\),如果自己確實有這個可能,給自己視角內的正確值投出 \(2^i\) 票,把前面所有人的票無效掉,否則不投票。

這樣子只會有最低位的 \(1\) 的票有效,而他假設自己是 \(1\),能夠投出正確值,唯一錯誤的情況是不存在 \(1\) 的全 \(0\) 情況。

Sub2

\(15\) 個人分成 \(1,2,4,8\) 四組套用 Sub3 做法即可。

錯誤的情況只有四組內部的答案均為 \(0\),算一下容易得到情況數是 \(2048\) 種可以透過。

Sub4

考慮一個人的視角用向量表示,看到的 \(k\) 個分別是 \(x_1,x_2,\cdots,x_k\)

如果我們有一個關於上述向量的函式 \(f\),它是一個整係數 \(k\) 次多項式,那麼我們可以把其中的某項交給能夠看到這些項的人投出其係數。

剛剛那句話很抽象,我們詳細解釋一下。

首先明確 \(f(\boldsymbol{v})\) 表示實際串為 \(\boldsymbol{v}\) 時,最後應該投出的總票數,我們設給奇數投票為正,偶數為負。

這裡 \(\boldsymbol{v}\) 是一個 \(n\) 維向量而非 \(k\) 維向量,這個向量的每一維都是 \(0\)\(1\),不妨設 \(\boldsymbol{v}=\{x_1,x_2,\cdots,x_n\}\)

假設 \(f\) 中有一項 \(-3x_1x_3x_5\),那麼我們可以讓一個能看到 \(x_1,x_3,x_5\) 的人來根據這三個變數的值決定是否投出 \(-3\) 票。

注意只能讓一個人投,多個人都投會導致重複,此時如果想要讓最後的答案式子的係數依舊是整數就很容易超過 \(10^8\) 的限制。

這之後我們再考慮 \(s=\sum\limits_{i=1}^{k}x_i\),投票的目的就是找出 \(s\) 的奇偶性。

所以我們嘗試找到一個關於 \(s\) 的多項式 \(g(s)\),我們令其也為一個 \(k\) 次多項式,則其最多有 \(k\) 個零點。

這個多項式的目標是在儘可能多的地方得到正確的正負性,因此需要找到適合的零點拐彎。

經過一些計算你可以得到最優的情況是在 \(0,2,11\) 得到錯誤的正負性(或者反過來 \(12,1,10\) 也是一樣的)。

此時錯誤的數量可以計算,為 \(\dbinom{12}{0}+\dbinom{12}{2}+\dbinom{12}{11}=79\) 種,正好符合要求。

寫出 \((x-p_1)\times\cdots\times(x-p_k)\) 這樣的式子,將 \(p_i=2.5+i\) 代入得到化簡後的式子。

乘上 \(-128\) 變成整係數後的式子是:

\(-128x^7+5824x^6-111776x^5+1172080x^4-7246232x^3+26389636x^2-52371534x+43648605\)

可以看到確實控制在 \(10^8\) 的限制內,並且比較極限。

接下來考慮怎麼把這個式子分給每個人投票,即把 \(g(s)\)\(f(\boldsymbol{v})\) 關聯起來。

回顧定義,\(s=\sum\limits_{i=1}^k x_i\)

將其代入,得到的也是一個 \(k\) 次多項式,即我們要求的 \(f\)

係數並不會炸,最大也就是對應次冪的係數乘上對應次數的階乘,目測一下就知道活得好好的。

這東西你就慢慢折磨去吧,總是能搞出來的,畢竟提答題不用考慮時間複雜度,寫個爆搜也是可以的。

具體實現不講留給讀者思考,也可以參考示例程式碼。


Code

#include<bits/stdc++.h>
using namespace std;
#define __FILE_MODE__
namespace Task_1
{
	//懶得寫了,直接看 Task2。
	//爆搜的思路就是根據看到的 $0$ 和 $1$ 的個數做出決策。
	//可以搜出 6906 種錯誤情況的策略,似乎還有更優一點的但不重要。
}
namespace Task_2
{
	constexpr int N=15;
	int ans[N][1<<N];
	inline int group(int x)//判斷 x 是哪個組的。
	{
		if(x<1)return 1;
		if(x<3)return 2;
		if(x<7)return 3;
		if(x<15)return 4;
        return -1;
	}
	inline void work(int n)
	{
#ifdef __FILE_MODE__
		freopen("vote2.ans","w",stdout);
#endif
		if((n+1)&n){printf("Invalid!");return;}//此策略僅對 $n=2^k-1$ 有效。
		for(int S=0;S<(1<<(n-1));S++)
		{
			for(int i=0;i<n;i++)
			{
				bool fl=true;//判斷前面是否全 0 的旗子。
				int gr=group(i);
				for(int j=1;j<gr;j++)
				{
					int sum=0;
					for(int k=0;k<(1<<j)-1;k++)
					{
						if(group(k)!=j)continue;
						sum^=(S>>k)&1;
					}
					if(sum!=0)fl=false;
				}
				if(fl)
				{
					int res=0;
					for(int j=gr+1;j<=4;j++)
					{
						int sum=0;
						for(int k=(1<<(j-1))-1;k<(1<<j)-1;k++)sum^=(S>>(k-1))&1;
						res^=sum;
					}
					ans[i][S]=res^1;
				}
				else ans[i][S]=i&1;
			}
		}
		for(int i=0;i<n;i++)
		{
			for(int S=0;S<(1<<(n-1));S++)printf("%d",ans[i][S]);
			putchar(10);
		}
		return;
	}
}
namespace Task_3
{
	constexpr int N=15;
	int ans[N][1<<N];
	inline void work(int n)
	{
#ifdef __FILE_MODE__
		freopen("vote3.ans","w",stdout);
#endif
		for(int S=0;S<(1<<(n-1));S++)
		{
			for(int i=0;i<n;i++)
			{
				bool fl=true;//判斷前面是否全 0 的旗子。
				for(int j=0;j<i;j++)if((S>>j)&1)fl=false;
				if(fl)
				{
					int res=0;
					for(int j=i;j<n-1;j++)res^=(S>>j)&1;
					if(res)ans[i][S]=-(1<<i);else ans[i][S]=(1<<i);
				}
				else ans[i][S]=0;
			}
		}
		for(int i=0;i<n;i++)
		{
			for(int S=0;S<(1<<(n-1));S++)printf("%d ",ans[i][S]);
			putchar(10);
		}
		return;
	}
}
namespace Task_4
{
	constexpr int N=12,K=7;
	constexpr int p[K+1]={43648605,-52371534,26389636,-7246232,1172080,-111776,5824,-128};
	constexpr int NUM=792;//共有 C(12,7)=792 人。
	int ans[NUM][1<<K];
	int sight[NUM],rev[1<<N];//每個人看到的實際編號。
	int bel[1<<N];//預處理出出現該情況時給誰投。
	inline int func(int sight,int fact)//處理出這個人的實際所見。
	{
		int ret=0,tot=0;
		for(int i=0;i<N;i++)if((sight>>i)&1)ret|=((fact>>i)&1)<<tot,tot++;
		return ret;
	}
	inline int ppc(int V)
	{
		int s=0;
		for(int i=0;i<N;i++)s+=(V>>i)&1;
		return s;
	}
	void DFS(int now,int pw)
	{
		if(pw>K)return;//次數過大。
		int id=bel[now];
		int unchanged=func(sight[id],now);
		for(int S=0;S<(1<<K);S++)if((S&unchanged)==unchanged)ans[id][S]+=p[pw];
		for(int i=0;i<N;i++)DFS(now|(1<<i),pw+1);
		return;
	}
	inline void work(int n,int k)
	{
#ifdef __FILE_MODE__
		freopen("vote4.ans","w",stdout);
#endif
		if(n!=12||k!=7)return;//此策略僅對 $n=12,k=7$ 有效。
		int tot=0;
		for(int V=0;V<(1<<n);V++)
		{
			int s=ppc(V);
			if(s==k)
			{
				sight[tot]=V;
				rev[V]=tot;
				tot++;
			}
			else rev[V]=-1;
		}
		for(int V=0;V<(1<<n);V++)
		{
			int s=ppc(V);
			if(s>k)continue;
			for(int i=0;i<NUM;i++){if((sight[i]&V)==V){bel[V]=i;break;}}
		}
		DFS(0,0);
		for(int i=0;i<NUM;i++)
		{
			for(int S=0;S<(1<<k);S++)printf("%d ",ans[i][S]);
			putchar(10);
		}
		return;
	}
}
int main()
{
	Task_2::work(15);
	Task_3::work(15);
	Task_4::work(12,7);
	return 0;
}

完整檔案有 1197.5kb 就不貼了,上面那個程式可以直接執行出來。

相關文章