題目描述:
vjudge題目鏈接
給出n個杯子,每個杯子有一(納)升的水,每次從一個杯子向另一個杯子倒水,使得後一個杯子的水變成原來的兩倍,求出一種操作方案,使得最終第一個杯子有m升水,或無解。
題目分析:
構造神題。
這裏的最下面是某位GrandMaster的題解:
驚爲天人。
然而考場還是有巨佬憑藉超強的找規律+揣測結論切了這道題。
我就概括一下上面的段意:
- ,此時一定有解,且很容易構造。
- ,要構造出這樣的,需要。
考慮最後一步,,不可能是,所以,所以 - ,要構造出這樣的,同樣需要。
因爲不是2的冪,所以可以找到一個奇質數使得,然而一開始所有數都是1,不能被p整除,在的過程中,如果中有一個不能被p整除,那麼中必然也有一個不能被p整除,所以如果,最終所有數都能被p整除,這是無法做到的,故。 - 接下來證明當時,一定可以構造出。
假設這時這樣一個命題成立(證明在下一段):可以被分成兩份:。
此時,那麼就有,因此在p步之後就會變成
由於兩個數中有一個是2的冪,由歐拉定理()知這個過程一定會出現中的一個數變成1,再下一步變成2,此時另一個數就是,即我們要求的 - 接下來證明上面的命題成立:
想象一下把拆成2進制,譬如 n = 16 + 4 + 1。 此時從末尾往前合併,差的值用第一個杯子來補
譬如第一步就變成 n = 15 + 4 + 2, 然後是 n = 13 + 4 + 4,然後是 n = 13 + 8,然後是 n = 5 + 16
這樣就可以將除了第一個杯子的剩下的數合併成2的冪。差的值是一定能夠補足的,因爲第一個杯子裏的水大於後面所有的和。
於是這道題就做完了。
最好想好實現再開始寫代碼,不然會很噁心。。打了1h+的我qwq。。。
Code:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,k,m,len[maxn];
vector<pair<int,int> >ans;
vector<int>t;
void solve(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
solve(l,mid),solve(mid+1,r);
ans.push_back(make_pair(mid+1,l));
}
int main()
{
scanf("%d%d",&n,&k);
if(k==(k&-k)){
for(int i=2;i<=k;i<<=1) m+=k/i;
printf("%d\n",m);
for(int i=2;i<=k;i<<=1)
for(int j=1;j<k;j+=i)
printf("%d %d\n",j+i/2,j);
return 0;
}
if(k&1){
if(n<=k+1) return puts("-1"),0;
n=k+2;
}
else{
if(n==k) return puts("-1"),0;
n=k+1;
}
for(int i=0;(1<<i)<=n;i++) if(n&1<<i) t.push_back(1<<i);
reverse(t.begin(),t.end());
for(int i=1,j=0;i<=n;i+=t[j++]) solve(i,i+t[j]-1),len[i]=t[j];
for(int o=t.size()-2,now=n+1-t[o+1],pre=now-t[o]; ;now=pre,pre=now-t[--o]){
while(len[pre]>len[now]){
ans.push_back(make_pair(1,now));
len[1]-=len[now],len[now]<<=1;
if(len[1]==k) goto hehe;
}
if(pre==1) break;
ans.push_back(make_pair(now,pre));
len[pre]+=len[now];
}
hehe:
int &x=len[1],&y=len[1+t[0]];
while(x!=k){
if(x<y) ans.push_back(make_pair(1+t[0],1));
else ans.push_back(make_pair(1,1+t[0]));
x=(x<<1)%n,y=(y<<1)%n;
}
printf("%d\n",ans.size());
for(auto i: ans) printf("%d %d\n",i.first,i.second);
}