施工中……
比賽小結
凈貢獻若干發罰時 /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);
}
}