1. 程式人生 > 其它 >Noip模擬96 2021.11.13

Noip模擬96 2021.11.13

T1 子集和 T2 異或 T3 異或2 T4 卡牌遊戲

T1 子集和

看出是一道揹包的切得都飛快,我沒用揹包於是做了很長時間。。。

因為程式碼太難調了!!!!

建議不要看我的,不過如果你想整活兒學一些\(nb\)的演算法可以看一看

我拿一個指標從\(m-1\)開始往前掃,發現越到後面的陣列成他用的集合的大小就越大

那麼從後往前掃,定義指標\(p\)為當前掃到的桶不為空的元素,那麼我們將會把\(m-p\)放入答案中

他一定會存在於\(a\)陣列中,且\(p\)就是除了這個數的剩下的數的加和

這樣我們每找到一個不空的桶就給他更新一個答案,同時對當前的\(a\)陣列中所有元素能夠組成的數進行統計

把能夠組成的數的桶該減的減掉

做法就是拿一個\(set\)

,記錄二元組\((val,cnt)\)表示一個數\(val\)出現的次數\(cnt\),每次加入答案陣列的時候都更新這個\(set\)裡面的元素

其實不難發現,如果程式碼寫的對的話,\(set\)中最後存的所有元素就是\(b\)陣列的下標以及其對應的桶的個數

subset(Very disgusting!!)
#include<bits/stdc++.h>
#define int long long
using namespace std; FILE *wsn;
auto read=[](){
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
};
const int NN=10001;
int n,m,b[NN],a[NN],top,pot,c[NN];
struct node{
	int val; mutable int cnt;
	bool operator<(const node&x)const{
		return val<x.val;
	}
};
set<node>S; node stk[NN],skt[NN];
inline void push(int x){
	for(auto s:S)
		if(S.find(node{x+s.val,0})==S.end()) stk[++top]=node{x+s.val,s.cnt};
		else skt[++pot]=node{x+s.val,s.cnt};
	while(pot) S.find(node{skt[pot].val,0})->cnt+=skt[pot].cnt,--pot;
	while(top) S.insert(node{stk[top].val,stk[top].cnt}),--top;
	if(S.find(node{x,0})==S.end()) S.insert(node{x,1});
	else S.find(node{x,0})->cnt++;
	for(auto s:S) c[s.val]=b[s.val]-s.cnt;
}
namespace WSN{
	inline short main(){
		wsn=freopen("subset.in","r",stdin);
		wsn=freopen("subset.out","w",stdout);
		n=read(); m=read();
		for(int i=0;i<=m;i++)c[i]=b[i]=read();
		int p=m-1,tmp=0,tim=0;
		while(tmp<n-1){
			while(!c[p]&&p) --p;
			if(!c[m-p]) c[p]=0;
			else --c[p],a[++tmp]=m-p,push(m-p);
		}int sm=0;
		for(int i=1;i<n;i++)sm+=a[i],printf("%lld ",a[i]);printf("%lld\n",m-sm);
		return 0;
	}
}
signed main(){return WSN::main();}

T2 異或

發現考場上打二維偏序的好像又只有我一個。。。。

好的剛剛發現了一個,sdfz兄弟很棒棒!!!

然而非常不服,發現常數小的\(O(n^3)\)居然也能拿\(40\)!!!很氣憤

不懂為啥能跑過\(8e9\)

正解是\(trie\)數思想或者說是跟二進位制位有關的,顯然。。

考慮\(i,k\)的每一位,發現他們的大小決策於從低到高的第一個不同的位上

那麼我們列舉每一位,設列舉的是第\(p\)位,再列舉從\(1\)\(n\)找到每一位上的數是啥,記一下高於第\(p\)位的數為\(res\),並進行離散化

那麼我們發現如果\(a_k>a_i\),有以下幾種情況

  1. \(a_k[p]=1,a_i[p]=0,a_j[p]=0\)
  2. \(a_k[p]=0,a_i[p]=1,a_j[p]=1\)

那麼我們列舉的\([1,n]\)相當於是在列舉\(a_k[p]\)

那麼我們只需要知道字首的\(0/1\)個數\(sum[0/1]\)

和當前的\(k\)\(res\)相同的\(i\)的字首的\(0/1\)的個數\(cnt[res][0/1]\)

以及需要容斥掉的算重的個數\(pre[res][0/1]\)即可在\(O(1)\)複雜度內計算答案

xor
#include<bits/stdc++.h>
#define int long long
using namespace std; FILE *wsn;
auto read=[](){
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
};
const int NN=5e5+5;
int n,a[NN],ans,id;
int sum[2],cnt[NN][2],pre[NN][2];
unordered_map<int,int>mp;
namespace WSN{
	inline short main(){
		wsn=freopen("xor.in","r",stdin);
		wsn=freopen("xor.out","w",stdout);
		n=read();for(int i=1;i<=n;i++)a[i]=read();
		for(int i=30;i;i--){
			sum[0]=sum[1]=0; id=0; mp.clear();
			memset(cnt,0,sizeof(cnt));
			memset(pre,0,sizeof(pre));
			for(int j=1;j<=n;j++){
				int it=a[j]>>i-1&1,res=a[j]>>i;
				// cout<<it<<endl;
				if(!mp[res])mp[res]=++id;
				res=mp[res];
				ans+=sum[it^1]*cnt[res][it^1]-pre[res][it^1];
				++sum[it]; ++cnt[res][it]; pre[res][it]+=sum[it];
			}
		}
		printf("%lld\n",ans);
		return 0;
	}
}
signed main(){return WSN::main();}

T3 異或2

珍愛生命,遠離高精

關於遞推式,我只想說第一次看見要推異或的式子的。。。

比較明顯是要按照奇偶分類討論的,剩下的直接看這個吧,感覺我寫出來也是一樣的,看著式子就比較好理解了

主要思想就是看每個數字的最後一位上是\(0/1\)對答案的貢獻

rox
#include<bits/stdc++.h>
#define int long long
using namespace std; FILE *wsn;
namespace BIG_INT{
	const int base=1e16;
	typedef __uint128_t ULL;
	struct Big_Int{
		int p[100];
		ULL has;
		Big_Int(){}
		Big_Int(int x){memset(p,0,sizeof(p));p[0]=1;p[1]=x;}
		inline void gethash(){has=p[0];for(int i=1;i<=p[0];i++)has*=p[i];}
		inline Big_Int READ(){
			Big_Int ret=Big_Int(0); char s[505];
			scanf("%s",s+1); int len=strlen(s+1);
			for(int i=1;i<=len;i++)ret=ret*10+Big_Int(s[i]-'0');
			return ret;
		}
		inline void print(){
			printf("%lld",p[p[0]]);
			for(int i=p[0]-1;i>0;--i) printf("%016lld",p[i]);
			puts("");
		}
		Big_Int operator*(const int&x)const{
			Big_Int ret=Big_Int(0);int add=0; ret.p[0]=p[0];
			for(int i=1;i<=ret.p[0];++i){
				ret.p[i]=p[i]*x+add;
				add=ret.p[i]/base;
				ret.p[i]-=add*base;
			}
			if(add) ret.p[++ret.p[0]]=add;
			ret.gethash();
			return ret;
		}
		Big_Int operator+(const Big_Int&a)const{
			Big_Int ret=Big_Int(0);int add=0; ret.p[0]=max(a.p[0],p[0]);
			for(int i=1;i<=ret.p[0];++i){
				ret.p[i]=a.p[i]+p[i]+add;
				add=ret.p[i]/base;
				if(ret.p[i]>=base) ret.p[i]-=base;
			}
			if(add) ret.p[++ret.p[0]]=add;
			ret.gethash();
			return ret;
		}
		Big_Int operator-(const int&x)const{
			Big_Int ret=Big_Int(0); ret.p[0]=p[0];
			for(int i=1;i<=ret.p[0];i++)ret.p[i]=p[i];
			ret.p[1]-=x; int pos=1;
			while(ret.p[pos]<0){
				ret.p[pos]+=base;
				++pos; ret.p[pos]--;
			}
			if(!ret.p[ret.p[0]]) ret.p[0]--;
			ret.gethash();
			return ret;
		}
		Big_Int operator/(const int&x)const{
			Big_Int ret=Big_Int(0); ret.p[0]=p[0];
			for(int i=1;i<=ret.p[0];i++)ret.p[i]=p[i];
			if(ret.p[1]&1) ret.p[1]--;
			for(int i=ret.p[0];i;i--){
				if(ret.p[i]&1) ret.p[i-1]+=base;
				ret.p[i]/=x;
			}
			if(!ret.p[p[0]]) --ret.p[0];
			ret.gethash();
			return ret;
		}
	};
	map<ULL,Big_Int> has;
}using namespace BIG_INT;

inline Big_Int dfs(Big_Int x){
	if(x.p[0]<=1&&x.p[1]<=0) return has[x.has]=Big_Int(0);
	if(has.find(x.has)!=has.end()) return has[x.has];
	Big_Int k=x/2;
	if(x.p[1]&1) has[x.has]=dfs(k)*4+k*6;
	else has[x.has]=dfs(k)*2+dfs(k-1)*2+k*4-4;
	return has[x.has];
}
Big_Int n,m,ans;
namespace WSN{
	inline short main(){
		wsn=freopen("rox.in","r",stdin);
		wsn=freopen("rox.out","w",stdout);
		n=n.READ();
		dfs(n).print();
		return 0;
	}
}
signed main(){return WSN::main();}

T4 卡牌遊戲(哪一天她能重回我身邊

重推這篇題解