2021 牛客多校 第六場 選做


比賽link

施工中……

比賽小結

凈貢獻若干發罰時 /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,列出式子:

\[f_i=\sum_{j>i,j\oplus k=i}f_j\times p_k+\sum_{j\oplus i\le i}f_i\times p_j \]

\(s_i=\sum_{j\oplus i\le i} p_j\),改寫一下:

\[f_i=\left(\sum_{j>i,j\oplus k=i}f_j\times p_k+1\right)\times \frac{1}{1-s_i} \]

計算 \(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

較難發現題意轉化后即為:

\[f(n)= \sum_{p\in Prime} \sigma_0\left(\frac{n}{p}\right) \]

\(\sum_{i=1}^n f(n)\)

交換求和號即為求:

\[\sum_{p\in Prime}\sum_{i=1}^{\frac{n}{p}} \sigma_0(i) \]

考慮整除分塊,對 \(\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);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM