早就听说NOI2015题是最简单的一套NOI题,即便如此我还是做得超级辣鸡。
程序自动分析这道'普及-'的sb并查集题先不提,软件包管理器这道'提高-'的树剖模板题我也无话可说。
我遇到的第一道,或许有省选难度的题是D1T3寿司晚宴。
看到的第一反应是做过的网络流里面有关二分图的模型,然而跟二分图一点关系都没有
我觉得肯定和什么根号有关但是很少做这样的题我也推不出来什么方法。
然后就弃疗打暴力,因为>n/2的素数只会在一个数中出现,所以这些素数可以不管它们,只用状压<=n/2的素数就可以了,100以内有15个,然后3进制状压写了个暴力,可以比30分暴力多骗10分?
正解确实和根号有关,我们考虑<=sqrt(n)的素数只有8个,每个数含有>sqrt(n)的素数最多只有一个。
对于不含有>sqrt(n)的素数的数,我们直接状压dp,对于含有>sqrt(n)的素数的数,我们按照那个素数是多少把他们分组,状压dp。
每一组的dp中,g[0/1][i][j]表示这一组由第0/1个人选的方案数.
每一组的初值g[0][i][j]=g[1][i][j]=f[i][j]
最后答案f[i][j]=g[0][i][j]+g[1][i][j]-f[i][j](减去这一组两个人都啥都没选)
这种利用“含>sqrt(n)的素数只有一个”这个特点的题似乎不少,但是我刷题太少了。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int maxn=500+7,maxt=(1<<8)+7; const ll prime[maxn]={2,3,5,7,11,13,17,19},totp=7,sz=(1<<8)-1; ll n,mod,f[2][maxt][maxt],g[2][maxt][maxt],ans; char cc; ll ff; template<typename T>void read(T& aa) { aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } bool is_ok(int x) { For(i,0,totp) while(x%prime[i]==0) x/=prime[i]; return x==1; } bool is_prime(int x) { for(int i=2;i*i<=x;++i) if(x%i==0) return 0; return 1; } void mo(ll& x) {if(x>=mod) x-=mod;} int get_num(int x) { int rs=0; For(i,0,totp) if(x%prime[i]==0) rs|=(1<<i); return rs; } int main() { read(n); read(mod); int o=0,x,up,r; f[0][0][0]=1; For(i,2,n) if(is_ok(i)){ o^=1; x=get_num(i); // For(s,0,sz) For(t,0,sz) f[o][s][t]=0; memset(f[o],0,sizeof(f[o])); For(s,0,sz) For(t,0,sz) if(f[o^1][s][t]) { f[o^1][s][t]%=mod; f[o][s][t]+=f[o^1][s][t]; if(!(x&t)) f[o][s|x][t]+=f[o^1][s][t]; if(!(x&s)) f[o][s][t|x]+=f[o^1][s][t]; } } For(i,23,n) if(is_prime(i)) { o^=1; up=n/i; For(s,0,sz) For(t,0,sz) f[o][s][t]=mod-(f[o^1][s][t]%=mod); For(s,0,sz) For(t,0,sz) g[0][s][t]=g[1][s][t]=f[o^1][s][t]; For(j,1,up) { x=get_num(j); Rep(s,sz,0) Rep(t,sz,0) if(!(x&t)) { r=g[0][s][t]; g[0][s|x][t]+=r; mo(g[0][s|x][t]); } Rep(s,sz,0) Rep(t,sz,0) if(!(x&s)) { r=g[1][s][t]; g[1][s][t|x]+=r; mo(g[1][s][t|x]); } } For(s,0,sz) For(t,0,sz) f[o][s][t]=(f[o][s][t]+g[0][s][t]+g[1][s][t])%mod; } For(s,0,sz) For(t,0,sz) ans+=f[o][s][t]; printf("%lld ",ans%mod); return 0; }
我遇到的第二道可能有省选难度的题是D2T1荷马史诗
看到这道题觉得自己有可能把它做出来,感觉其实挺对我胃口的(如果我还记得哈弗曼树的话)
而且这种"合并"类型的题也不是没做过,我或许还A过几道?
后来弃疗写了个像是O(n^2log(n))的dp,期望45结果写挂了只有30。
我是先把这道题看作在trie树上选n个位置,并且这n个位置不存在祖孙关系,所以深度就是长度。
因为我们知道,如果确定了n个位置,那么肯定是出现次数多的深度小。
那么我们就考虑深度从小到大加点。dp[i][j]表示处理到出现次数第i大的点目前这一层有j个位置可选的最小代价,len[i][j]表示处理到出现次数第i大的点目前这一层有j个位置可选在最小代价下的最小深度。
初始时dp[0][k]=w[1]、len[0][k]=1
算dp[i]的时候然后枚举这个点的深度比上个点深度多多少,如果可选位置数目>=n-i了就不用再往下走了。
正解就是k叉哈弗曼树,注意一开始要把个数补到(k-1)的倍数。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<queue> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int maxn=1e5+7; ll n,k,ans; char cc; ll ff; template<typename T>void read(T& aa) { aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } struct Node{ ll dep,w; Node(){} Node(ll dep,ll w):dep(dep),w(w){} bool operator < (const Node& b) const{return w==b.w? dep>b.dep:w>b.w;} }o; priority_queue<Node> G; int main() { read(n); read(k); ll x,d; For(i,1,n) { read(x); G.push(Node(0,x)); } if((n-1)%(k-1)!=0) Rep(i,k-1-(n-1)%(k-1),1) G.push(Node(0,0)); while(G.size()>1) { x=0; d=0; For(i,1,k) { o=G.top(); G.pop(); x+=o.w; d=max(d,o.dep); ans+=o.w; } G.push(Node(d+1,x)); } o=G.top(); printf("%lld %lld ",ans,o.dep); return 0; }
至于品酒大会……
以前做过2次,这是第3次做了,给个链接:http://www.cnblogs.com/Serene-shixinyi/p/8321281.html