OpenCup 2015: Chemistry【構造(完全不可能想得到)題】

題目描述:

vjudge題目鏈接
給出n個杯子,每個杯子有一(納)升的水,每次從一個杯子向另一個杯子倒水,使得後一個杯子的水變成原來的兩倍,求出一種操作方案,使得最終第一個杯子有m升水,或無解。

題目分析:

構造神題。
這裏的最下面是某位GrandMaster的題解:
在這裏插入圖片描述
驚爲天人。
然而考場還是有巨佬憑藉超強的找規律+揣測結論切了這道題。
我就概括一下上面的段意:

  • k=2xk=2^x,此時一定有解,且很容易構造。
  • k=2x1k=2x-1,要構造出這樣的kk,需要n2x+1n\ge2x+1
    考慮最後一步,(a,b)(ab,2b)(a,b)\rarr(a-b,2b)2b2b不可能是2x12x-1,所以ab=2x1a-b=2x-1,所以a2x,b1,na+b=2x+1a\ge2x,b\ge1,n\ge a+b=2x+1
  • k=2xk=2x,要構造出這樣的kk,同樣需要n2x+1n\ge2x+1
    因爲kk不是2的冪,所以可以找到一個奇質數pp使得pkp|k,然而一開始所有數都是1,不能被p整除,在(a,b)(ab,2b)(a,b)\rarr(a-b,2b)的過程中,如果a,ba,b中有一個不能被p整除,那麼ab,2ba-b,2b中必然也有一個不能被p整除,所以如果n=2xn=2x,最終所有數都能被p整除,這是無法做到的,故n2x+1n\ge2x+1
  • 接下來證明當n=2x+1n=2x+1時,一定可以構造出k=2x2x1k=2x或2x-1
    假設這時這樣一個命題成立(證明在下一段):nn可以被分成兩份:n=2t+(n2t)n=2^t+(n-2^t)
    此時a+b=na+b=n,那麼就有(ab,2b)=(2a%n,2b%n)(a-b,2b)=(2a\%n,2b\%n),因此在p步之後(a,b)(a,b)就會變成(2pa%n,2pb%n)(2^p*a\%n,2^p*b\%n)
    由於兩個數中有一個是2的冪,由歐拉定理(aφ(p)1 (mod p)a^{\varphi(p)}\equiv1 ~(mod~p))知這個過程一定會出現a,ba,b中的一個數變成1,再下一步變成2,此時另一個數就是n1,n2n-1,n-2,即我們要求的kk
  • 接下來證明上面的命題成立:
    想象一下把nn拆成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);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章