题解
practice link
B. Believer
二分即可。
#include<bits/stdc++.h>
typedef unsigned long long ll;
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll calc(ll x,ll j){
return x*(x+1)/2*(1ll<<j);
}
ll ans;
inline ll work(ll x){
ll sum=0;
ans=0;
for(ll j=0;(1ll<<j)<=x;++j){
ll now=x/(1ll<<j);
ans+=now;
sum+=calc(now,j);
}
return sum;
}
int main(){
int t=read();
while(t--){
ll n=read(),maxn=1000000000;
ll l=1,r=min(maxn,n);
while(l<r){
ll mid=(l+r+1)>>1;
if(work(mid)<=n)l=mid;
else r=mid-1;
}
n-=work(l);
for(ll j=0;(1ll<<j)<=l+1;++j){
if((l+1)%(1ll<<j)==0){
if(n>=l+1){
n-=(l+1);
++ans;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
D.Do I Wanna Know?
首先容易写出 d p dp dp 柿子: f [ i ] [ j ] = p j f [ i − 1 ] [ j ] + ( 1 − p ) i − j f [ i − 1 ] [ j − 1 ] f[i][j]=p^jf[i-1][j]+(1-p)^{i-j}f[i-1][j-1] f[i][j]=pjf[i−1][j]+(1−p)i−jf[i−1][j−1]但这难以化简。于是我们考虑(打表 )发现 f [ i ] [ j ] = f [ i ] [ i − j ] f[i][j]=f[i][i-j] f[i][j]=f[i][i−j]。意为对称性很强,且把概率互换答案不变。于是我们可以再算一次: f [ i ] [ j ] = ( 1 − p ) j f [ i − 1 ] [ j ] + p i − j f [ i − 1 ] [ j − 1 ] f[i][j]=(1-p)^jf[i-1][j]+p^{i-j}f[i-1][j-1] f[i][j]=(1−p)jf[i−1][j]+pi−jf[i−1][j−1]这时联立可解得: f [ i ] [ j ] = p i − j + 1 − ( 1 − p ) i − j + 1 p j − ( 1 − p ) j f [ i ] [ j − 1 ] f[i][j]=\frac{p^{i-j+1}-(1-p)^{i-j+1}}{p^j-(1-p)^j}f[i][j-1] f[i][j]=pj−(1−p)jpi−j+1−(1−p)i−j+1f[i][j−1]这样即可递推。
但发现若 p = 1 2 p=\frac{1}{2} p=21 时无法递推,那么直接计算 f [ i ] [ j ] = C i j × ( 1 2 ) j ( i − j ) f[i][j]=C_i^j\times(\frac{1}{2})^{j(i-j)} f[i][j]=Cij×(21)j(i−j)
#include<bits/stdc++.h>
#define N 600005
typedef long long ll;
using namespace std;
const ll mod=998244353;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
ll jc[N],jcinv[N];
inline ll power(ll x,ll c){
ll now=1;x%=mod;
while(c){
if(c&1)now=1ll*now*x%mod;
x=1ll*x*x%mod;c>>=1;
}
return now;
}
inline void init(ll m){
jc[0]=1;
for(ll i=1;i<=m;++i)jc[i]=1ll*jc[i-1]*i%mod;
jcinv[m]=power(jc[m],mod-2);
for(ll i=m-1;i+1;--i)jcinv[i]=1ll*jcinv[i+1]*(i+1)%mod;
}
inline ll C(ll n,ll m){
if(n<m)return 0;
return 1ll*jc[n]*jcinv[m]%mod*jcinv[n-m]%mod;
}
ll f[N],g[N];
inline ll frac(ll n,ll m){
return 1ll*n*power(m,mod-2)%mod;
}
int main(){
ll n=read();
init(N-1);
ll a=read(),b=read(),p=1ll*a*power(b,mod-2);
g[1]=1;
for(ll i=2;i<=n;++i)g[i]=(1ll*g[i-1]*g[i-1]%mod+2)%mod;
if(b%a==0&&b/a==2){
for(ll i=1;i<n;++i){
f[i]=1ll*C(n,i)*power(p,i*(n-i)%(mod-1))%mod;
g[i]=(g[i-1]+1ll*g[i]*f[i]%mod)%mod;
}
g[n-1]=(g[n-1]+mod)%mod;
cout<<g[n-1]<<endl;
return 0;
}
f[0]=1;
for(ll i=1;i<n;++i){
f[i]=1ll*f[i-1]*(power(p,n-i+1)-power(1-p,n-i+1)+mod)%mod*power(power(p,i)-power(1-p,i)+mod,mod-2)%mod;
g[i]=(g[i-1]+1ll*g[i]*f[i]%mod)%mod;
}
g[n-1]=(g[n-1]+mod)%mod;
printf("%lld\n",g[n-1]);
return 0;
}
/*
4
1 3
*/
F.Forever and Always
手模几组后会发现,不可能出现无解情况。(不知道怎么证明)
之后尝试构造一组方案即:第 i i i 轮投票给 i i i 号候选者的人转为投票给 1 1 1 号候选者。这样很容易想到构造第 i i i 号候选者起初有 i − 1 i-1 i−1 个支持者,后面第
i i i 轮有一位 i i i 的支持者转投票给 1 1 1。这样总人数为 p 2 p^2 p2 级别。
但这还不够优。实际上在构造小数据时就应该想到是否能重复出现某一个小方案的循环。即每一次使 “1” 增加时引发下一个 “1” 的增加实现循环。
这时我们考虑下面的构造方法:
( 1 ) , ( 1 ) , ( 2 , 1 ) , ( 3 , 1 ) , ( 3 , 4 ) , [ ( 4 ) , ( 4 ) , ( 5 , 4 ) , ( 5 , 6 ) ] , [ ( 6 ) , ( 6 ) , ( 7 , 6 ) , ( 7 , 8 ) ] , . . . (1),\ (1),\ (2,1),\ (3,1),\ (3,4),\ [(4),\ (4),\ (5,4),\ (5,6)],\ [(6),\ (6),\ (7,6),\ (7,8)],... (1), (1), (2,1), (3,1), (3,4), [(4), (4), (5,4), (5,6)], [(6), (6), (7,6), (7,8)],...
这样除了最开始的引发人,之后会出现循环节,且只有当一个循环节循环完时才会引发下一个循环节。总人数为 2 p 2p 2p。
#include<bits/stdc++.h>
using namespace std;
int main(){
int n=205,m=1005;
printf("%d %d\n",n,m);
puts("2 1 1");
puts("2 1 1");
puts("2 2 1");
puts("2 3 1");
puts("2 3 4");
n-=5;
int now=4;
for(int i=1;i<=n/4;++i){
printf("2 %d %d\n",now,now);
printf("2 %d %d\n",now,now);
printf("2 %d %d\n",now+1,now);
printf("2 %d %d\n",now+1,now+2);
now+=2;
}
return 0;
}