最近我部落格似乎出了點 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 就不貼了,上面那個程式可以直接執行出來。