Noip模拟85 2021.10.29(致郁四部曲)
前言
感谢出题人,我考场上直接被致郁
虽然就只看过《Darling in the FRANXX》,还是为了\(ichigo\)难受了半天
T1 莓良心\((ichigo)\)
我打扮成你喜欢的样子来看你了,广,不,da,darling...
难受啊!!!可是决定切\(T1\)的我没能切掉也很难受
考场上一看就看过一部番,剩下的就不管了,就打\(T1\)
然而还是只能说容斥这个东西过于强大了,考场上打表+\(dp\)推了半天也没推出来的斯特林数第\(n\)行,容斥两三下就搞定了
\(\begin{Bmatrix}n\\k\end{Bmatrix}=\frac{1}{k!}\sum\limits_{i=0}^{k}\binom{k}{i}(k-i)^{n}\)
解释一下就是枚举\(k\)个集合里面有几个空集,那么选出这些空集后剩下的数随便放在\((k-i)\)个非空集合中,然后乘上容斥系数,然后再乱排\(k!\)
那么这道题就很简单了,发现每一种岩浆能源做贡献的次数相等,那么只考虑第一个岩浆能源做贡献的次数即可
不难得出是\(\begin{Bmatrix}n\\k\end{Bmatrix}+(n-1)\times\begin{Bmatrix}n-1\\k\end{Bmatrix}\)
所以答案就是\(ans=\sum w\times(\begin{Bmatrix}n\\k\end{Bmatrix}+(n-1)\times\begin{Bmatrix}n-1\\k\end{Bmatrix})\)
ichigo
#include<bits/stdc++.h>
#define int long long
#define si(i,x) for(int i=head[x],y=e[i].to;i;i=e[i].next,y=e[i].to)
using namespace std;
const int NN=1e6+5,mod=998244353;
namespace AE86{
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
}using namespace AE86;
int n,k,sm,pw[NN][2],prime[NN],len,h[NN],v[NN];
bool vis[NN];
auto qmo=[](int a,int b,int ans=1){
int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
return ans;
};
auto inv=[](int x){return qmo(x,mod-2);};
auto prework=[](){
pw[1][0]=pw[1][1]=1; h[0]=h[1]=1;
for(int i=2;i<=k;i++){
h[i]=h[i-1]*i%mod;
if(!vis[i]) prime[++len]=i,pw[i][0]=qmo(i,n),pw[i][1]=qmo(i,n-1);
for(int j=1;j<=len&&prime[j]*i<=k;j++){
vis[i*prime[j]]=1;
pw[i*prime[j]][1]=pw[i][1]*pw[prime[j]][1]%mod;
pw[i*prime[j]][0]=pw[i][0]*pw[prime[j]][0]%mod;
if(i%prime[j]==0){
break;
}
}
}
v[k]=inv(h[k]); v[0]=v[1]=1;
for(int i=k-1;i>=2;i--) v[i]=v[i+1]*(i+1)%mod;
};
auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
auto S=[](int opt,int k){
int ans=qmo(h[k],mod-2),tmp=0;
for(int i=0,bs=1;i<=k;i++,bs*=-1)
tmp=(tmp+bs*C(k,i)*pw[k-i][opt]%mod+mod)%mod;
return ans*tmp%mod;
};
namespace WSN{
inline short main(){
freopen("ichigo.in","r",stdin);
freopen("ichigo.out","w",stdout);
n=read(); k=read(); prework();
for(int i=1;i<=n;i++) sm=(sm+read())%mod;
int tmp=sm*((S(0,k)+(n-1)*S(1,k)%mod)%mod)%mod;
write(tmp);
return 0;
}
}
signed main(){return WSN::main();}
T2 尽梨了\((eriri)\)
我成为你心中的第一位了吗?
考场上码出来了所有的\(dp\),唯独就没有给数组排序
因为觉得有\(dp\)了贪心没骂意义,于是就假死了。。。
于是就发现了并换成了\(permutation\)。。。。
考虑如果在时刻\(t\)在商店\(i\)购买物品,结束后立即去商店\(j\)购买物品
那么\(j\)会因为在\(i\)处等候而额外花费\((a_i\times t+b_i+1)\times a_j\)的时间
如果我们将二者交换顺序,在时刻\(t\)在\(j\)购买,
结束后立即去\(i\)购买,\(i\)会额外花费\((a_j\times t+b_j+1)\times a_i\)的时间
若先去\(i\)比先去\(j\)更优,就需要满足
\((a_i\times t+b_i+1)\times a_j\leq(a_j\times t+b_j+1)\times a_i\)
即
\((b_i+1)\times a_j\leq(b_j+1)\times a_i\)
然后按照这个去排序,发现\(a_i=0\)的会在最后,那么找到他们按照\(b\)升序排序
先把前面的\(a_i>0\)的按照\(dp\)做一遍
设\(dp_{i,j}\)表示考虑了\(i\)个商店,选择购买了\(j\)个商店的物品需要花费的最少时间
那么转移方程为
\(dp_{i,j}=\min(dp_{i-1,j},dp_{i-1,j-1}+1+(dp_{i-1,j-1}+1)\times a_i+b_i)\)
然后把剩下的\(b\)看看最多能选几个就行了
eriri
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=2e5+5;
namespace AE86{
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
}using namespace AE86;
int n,T,f[NN][31],cnt,res,lg,ans;
struct node{
int a,b;
friend bool operator<(node x,node y){
return (x.b+1)*y.a<(y.b+1)*x.a;
}
}s[NN];
namespace WSN{
inline short main(){
freopen("eriri.in","r",stdin);
freopen("eriri.out","w",stdout);
n=read(); T=read();
for(int i=1;i<=n;i++)
s[i].a=read(),s[i].b=read();
sort(s+1,s+n+1);
for(int i=n;i;i--) if(!s[i].a) ++cnt;
sort(s+n-cnt+1,s+n+1,[](node x,node y)->bool{return x.b<y.b;});
res=n-cnt;
memset(f,0x3f,sizeof(f));
for(int i=0;i<=res;i++) f[i][0]=0;
for(int i=1;i<=res;i++){
for(int j=1;j<=min(i,30ll);j++) f[i][j]=f[i-1][j];
for(int j=1;j<=min(i,30ll);j++){
if(f[i-1][j-1]>1e18) continue;
if(f[i-1][j-1]+1+(f[i-1][j-1]+1)*s[i].a+s[i].b<=T)
f[i][j]=min(f[i][j],f[i-1][j-1]+1+(f[i-1][j-1]+1)*s[i].a+s[i].b);
}
}
for(int i=0;i<=min(res,30ll);i++) if(f[res][i]<=T) ans=max(ans,i);
int sm=0;
for(int i=res+1;i<=n;i++){
sm+=s[i].b+1;
for(int j=1;j<=min(res,30ll);j++)
if(sm+f[res][j]<=T)
ans=max(ans,j+i-res);
}
write(ans);
return 0;
}
}
signed main(){return WSN::main();}
T3 团不过\((yui)\)
眼泪没能流出来。因为已经哭过很多次了。
考虑在限定每堆石子数目互不相同的前提下,用所有方案数减去先手必败的方案数
设\(p_i=(2^n-1)^{\underline{i}}\),即\(i\)堆石子的总方案数
设\(f_i\)表示\(i\)堆石子时先手必败的方案数
我们考虑让前\(i-1\)堆石子任意取,通过调整最后一堆石子的数目使得异或和为\(0\),
方案数\(p_{i-1}\)
若前\(i-1\)堆石子异或和为\(0\),因为最后一堆不能取\(0\),这种情况是不合法的
,方案数为\(f_{i-1}\)
若前\(i-1\)堆石子中,有\(i-2\)堆石子异或起来是\(0\),那么最后一堆石子就只能和另一堆石子数目相同,也是不合法的,方案数为\((i-1)\times(2^n-i+1)\times f_{i-2}\)
于是得到\(f_i=p_{i−1}−f_{i−1}-(i-1)\times(2^n-i+1)\times f_{i-2}\),边界为
\(f_1=f_2=0\),直接\(O(n)\)递推即可。
好久没有见过题解给错式子了(疯狂作死)
yui
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7,NN=1e7+5;
namespace AE86{
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
}using namespace AE86;
int n,tot,f[NN],p[NN];
auto qmo=[](int a,int b,int ans=1){
int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
return ans;
};
auto inv=[](int x){return qmo(x,mod-2);};
auto prework=[](){
p[0]=1; f[1]=f[2]=0; int pw2=qmo(2,n);
for(int i=1;i<=n;i++) p[i]=(pw2-i+mod)%mod*p[i-1]%mod;
for(int i=3;i<=n;i++) f[i]=p[i-1]-f[i-1]-(i-1)*((pw2-i+1+mod)%mod)%mod*f[i-2]%mod;
};
namespace WSN{
inline short main(){
freopen("yui.in","r",stdin);
freopen("yui.out","w",stdout);
n=read(); prework();
write((p[n]-f[n]+mod)%mod);
return 0;
}
}
signed main(){return WSN::main();}
T4 七负我\((nanami)\)
既然真白你说不要的话,神田君就归我了哦。
\(\textit{meet in the middle}\),转化题意为寻找最大的团
其中团定义为完全图,也就是找最大的完全子图
那么直接枚举一半的完全子图然后通过两半之间的连边寻找最大的完全子图
然后均分打工时长并计算贡献即可,证明比较复杂,可以使用调整法然而我也不太会调整法
nanami
#include<bits/stdc++.h>
#define int long long
#define bt __builtin_popcountll
using namespace std;
const int NN=43;
namespace AE86{
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
auto swap_=[](int&a,int&b){a^=b^=a^=b;};
}using namespace AE86;
int n,m,x,e[NN],ans,lim,f[1<<20],g[NN];
inline bool check(int s,int opt){
for(int i=1;i<=lim;i++) if(s&(1<<i-1))
if((s^(1<<i-1)|e[i+opt*lim])!=e[i+opt*lim]) return 0;
return 1;
}
namespace WSN{
inline short main(){
freopen("nanami.in","r",stdin);
freopen("nanami.out","w",stdout);
n=read(); m=read(); x=read(); lim=n+1>>1;
for(int i=1,u,v;i<=m;i++){
u=read(),v=read();
if(u>v) swap_(u,v);
if(v<=lim){
e[u]|=1<<v-1;
e[v]|=1<<u-1;
}else if(u>lim){
e[u]|=1<<v-1-lim;
e[v]|=1<<u-1-lim;
}else g[v]|=1<<u-1;
}
for(int i=1;i<(1<<lim);i++) if(check(i,0))
ans=max(ans,f[i]=bt(i));
for(int s=1;s<(1<<lim);s++)
for(int i=1;i<=lim;i++)
if(!(s&(1<<i-1))) f[s|(1<<i-1)]=max(f[s|(1<<i-1)],f[s]);
for(int s=1;s<(1<<lim);s++) if(check(s,1)){
int all=(1<<lim)-1;
for(int i=1;i<=lim;i++) if(s&(1<<i-1)) all&=g[i+lim];
ans=max(ans,bt(s)+f[all]);
}
double wsn=(1.0*x/ans)*(1.0*x/ans)*(ans-1)*ans/2.0;
printf("%.6lf\n",wsn);
return 0;
}
}
signed main(){return WSN::main();}