2021 牛客多校 第六场 选做
施工中……
比赛小结
净贡献若干发罚时 /cy
D. Gambling Monster
题意
有一个转盘,每次转动得到 \(0\sim n-1\)(\(n\) 为 \(2\) 的次幂)的概率分别给出。最开始你有一个数 \(x\),每次转动转盘得到一个数 \(y\),如果 \(x\oplus y>x\) 就令 \(x=x\oplus y\),否则 \(x\) 不变。求使 \(x=n-1\) 期望转动转盘的次数。
Solution
从后往前 dp,列出式子:
设 \(s_i=\sum_{j\oplus i\le i} p_j\),改写一下:
计算 \(s_i\) 比较简单,我们可以枚举 \(j\) 的最高位,用前缀和计算一下即可。
计算 \(\sum_{j>i,j\oplus k=i}f_j\times p_k\),发现形式为异或卷积且为后面贡献前面,可以考虑将 fwt 结合分治 fft,即分治 fwt。我们统计 \([mid+1,r]\) 贡献到 \([l,mid]\) 的答案,那么如何找到对应的 \(k\)?发现两个区间的数显然是前面若干位相同的(和为 \(l\)),中间一位会相反,手玩一下即可找到对应的 \(k\) 区间其实就是 \([mid+1-l,r-l]\)(考场上不会呜呜呜)。
code
#include<bits/stdc++.h>
using namespace std;
inline int gi()
{
char c=getchar(); int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int N=(1<<17)+5,Mod=1e9+7,inv2=(Mod+1)/2;
int n,p[N],sp[N],s[N],f[N];
vector<int> a,b;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{ return (x+y>=Mod?x+y-Mod:x+y);
}
inline int sub(int x, int y)
{ return (x-y<0?x-y+Mod:x-y);
}
inline int po(int x, int y)
{
int r=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
return r;
}
void fwt(vector<int>& a, int o)
{
const int n=a.size();
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=(i<<1))
for(int k=0;k<i;++k)
{
int x=a[j+k],y=a[i+j+k];
a[j+k]=add(x,y),a[i+j+k]=sub(x,y);
if(o==-1) a[j+k]=mul(a[j+k],inv2),a[i+j+k]=mul(a[i+j+k],inv2);
}
}
void cdq(int l, int r)
{
if(l==r)
{
if(l==n-1) f[l]=0;
f[l]=mul(s[l],add(f[l],1));
return ;
}
int mid=l+r>>1;
cdq(mid+1,r);
a.clear(),b.clear();
for(int i=mid+1;i<=r;++i) a.push_back(f[i]),b.push_back(p[i-l]);
fwt(a,1),fwt(b,1);
for(int i=0;i<a.size();++i) a[i]=mul(a[i],b[i]);
fwt(a,-1);
for(int i=l;i<=mid;++i) f[i]=add(f[i],a[i-l]);
cdq(l,mid);
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int iv=0;
for(int i=0;i<n;++i) p[i]=gi(),iv=add(iv,p[i]);
iv=po(iv,Mod-2);
for(int i=0;i<n;++i) p[i]=mul(p[i],iv),sp[i]=add(sp[i-1],p[i]);
for(int i=0;i<n;++i)
{
s[i]=f[i]=0;
for(int j=0;(1<<j)<=i;++j) if((1<<j)&i)
s[i]=add(s[i],sub(sp[(1<<j+1)-1],sp[(1<<j)-1]));
s[i]=add(s[i],p[0]);
s[i]=po(sub(1,s[i]),Mod-2);
}
cdq(0,n-1),printf("%d\n",f[0]);
}
}
G. Hasse Diagram
Solution
较难发现题意转化后即为:
求 \(\sum_{i=1}^n f(n)\)
交换求和号即为求:
考虑整除分块,对 \(\lfloor\frac{n}{i}\rfloor=x\) 的块,答案即为块内质数个数乘上 \(\sum_{i=1}^x \sigma_0(i)\),前者质数个数和为经典问题,使用 min25 筛解决;后者也是经典问题,使用整除分块解决,需预处理前若干项。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,Mod=1145140019;
typedef long long ll;
bool p[N];
int pri[N],tot,wcnt,ans,sum[N];
ll n,m,w[N],id1[N],id2[N],g[N];
inline int gid(ll x)
{ return x<=m?id1[x]:id2[n/x];
}
void init()
{
for(int i=2;i<N;++i)
{
if(!p[i]) pri[++tot]=i;
for(int j=1;j<=tot&&i*pri[j]<N;++j)
{
p[i*pri[j]]=true;
if(i%pri[j]==0) break;
}
}
for(int i=1;i<N;++i)
for(int j=i;j<N;j+=i) ++sum[j];
for(int i=1;i<N;++i) sum[i]=(sum[i]+sum[i-1])%Mod;
}
int sumd(ll n)
{
if(n<N) return sum[n];
int res=0;
for(ll i=1,j;i<=n;i=j+1)
{
j=n/(n/i);
res=(res+(j-i+1)*(n/i))%Mod;
}
return res;
}
int main()
{
init();
int T; scanf("%d",&T);
while(T--)
{
scanf("%lld",&n),m=sqrt(n),wcnt=ans=0;
for(ll i=1,j;i<=n;i=j+1)
{
j=n/(n/i),w[++wcnt]=n/i;
g[wcnt]=w[wcnt]-1;
n/i<=m ? id1[n/i]=wcnt : id2[j]=wcnt;
}
for(int j=1;j<=tot;++j)
for(int i=1;i<=wcnt&&1ll*pri[j]*pri[j]<=w[i];++i)
g[i]-=g[gid(w[i]/pri[j])]-(j-1);
for(int i=1;i<=wcnt;++i) g[i]%=Mod;
for(ll i=2,j;i<=n;i=j+1)
{
j=n/(n/i);
int tmp=(g[gid(j)]-g[gid(i-1)]+Mod)%Mod;
ans=(ans+1ll*sumd(n/i)*tmp)%Mod;
}
printf("%d\n",ans);
}
}
I. Intervals on the Ring
开个坑纪念一下自己想假了的签到题 QAQ
Solution
一开始以为合并后有且仅有 \(\le 2\) 个区间时有解,但实际上本题一定有解,只需对每段空白区间求补集区间即可,当然甚至也可以对每个空白点求补集。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
pair<int,int> a[N];
vector<pair<int,int>> v;
int main()
{
int T; scanf("%d",&T);
while(T--)
{
int n,m; scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) scanf("%d%d",&a[i].first,&a[i].second);
sort(a+1,a+1+m);
v.clear();
for(int i=2;i<=m;++i)
v.push_back(make_pair(a[i].first,a[i-1].second));
v.push_back(make_pair(a[1].first,a[m].second));
printf("%d\n",v.size());
for(auto x:v) printf("%d %d\n",x.first,x.second);
}
}
J. Defend Your Country
题意
给你一个 \(n\) 个点 \(m\) 条边简单无向连通图,你可以删掉若干边,最终每个连通块的贡献为 \((-1)^{\text{连通块大小}}\sum a_i\)。求最大贡献。
\(n\le 10^6\).
Solution
呜呜呜考场上猜到了可以只删一个点,但忘了除了可以删非割点还可以删一个能使剩下连通块大小为偶数的点。
做法直接 tarjan 求下割点顺便记下子树大小即可,注意一些细节。
然后补题时又因为 \(n,m\) 开成局部变量调了一万年呜呜呜。
可以只删一个点的证明过程参见官方题解。
code
#include<bits/stdc++.h>
using namespace std;
namespace io {
const int SIZE=(1<<21)+1;
char ibuf[SIZE],*iS,*iT,c; int qr;
#define gc()(iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),(iS==iT?EOF:*iS++)):*iS++)
inline int gi (){
int x=0,f=1;
for(c=gc();c<'0'||c>'9';c=gc())if(c=='-')f=-1;
for(;c<='9'&&c>='0';c=gc()) x=(x<<1)+(x<<3)+(c&15); return x*f;
}
} using io::gi;
const int N=1e6+5;
vector<int> e[N];
int n,m,a[N],low[N],dfn[N],sze[N],tid,mn;
bool cut[N],odd[N];
void dfs(int u)
{
low[u]=dfn[u]=++tid,sze[u]=1;
int ch=0;
for(auto v:e[u])
if(!dfn[v])
{
++ch, dfs(v);
sze[u]+=sze[v];
if(dfn[u]<=low[v])
{
if(u!=1) cut[u]=true;
if(sze[v]&1) odd[u]=true;
}
low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
if(u==1&&ch>1) cut[u]=true;
}
void init()
{
tid=0,mn=1<<30;
for(int i=1;i<=n;++i) sze[i]=odd[i]=cut[i]=low[i]=dfn[i]=0,e[i].clear();
}
int main()
{
int T=gi();
while(T--)
{
init();
n=gi(),m=gi();
long long sum=0;
for(int i=1;i<=n;++i) a[i]=gi(),sum+=a[i];
for(int i=1;i<=m;++i)
{
int u=gi(),v=gi();
e[u].push_back(v),e[v].push_back(u);
}
if(~n&1)
{
printf("%lld\n",sum);
continue;
}
dfs(1);
for(int i=1;i<=n;++i) if(!cut[i]||!odd[i]) mn=min(mn,a[i]);
printf("%lld\n",sum-mn-mn);
}
}