2019南京网络赛(2019 ICPC Asia Nanjing) (A(BIT/cdq)、B(欧拉降幂)、C(NTT)、D(概率dp)、E(杜教筛+积性函数线性筛)、F、H、G题待补、I题待补)

竞赛编程技巧与算法解析
本文深入探讨了网络赛中的数论难题,分享了在HolyGrail和GreedySequence问题上的解题心得,同时提供了详尽的代码示例。文章还讲解了Thebeautifulvaluesofthepalace、super_log、Tsy'snumber5、Robots和KSum等题目的解题思路,涵盖了树状数组、CDQ分治、欧拉降幂、NTT、概率DP、杜教筛等多种高级算法。

心得

两个数论不会,惨遭爆零,我太菜了……

网络赛好难啊,我太难了……

赛中通过

H.Holy Grail(签到/6次SPFA)

题意

n(n<=300)个点,m(m<=500)条边的有向图,无重边自环负环,但有负权边

要求你加六条边,第i次的图是建立在第i-1次加边加好的前提下的,

第i次给定si ti,要求添加最小的权值的边si->ti,

可以加负边,但要求使得加边之后的图无负环

题目保证,初始情况下无si->ti的边

题解

每次以t为源点跑最短路,找到t->s的最短路p,加权值为-p的边构成零环即可,

加完边之后,重新跑最短路

号被销了 代码找不到了

F. Greedy Sequence(set维护区间最值)

题意

给定n(n<=1e5)的一个排列a[],和一个k

对于每个长度i(1<=i<=n),要求构造一个长度为n的s[]序列,满足

①第一个数是i ②s[]中相邻的两个数,满足后数比前数小

③s[]中相邻的两个数,在a[]中对应的位置,不能超过给定的k

④对于某一位置,若找不到这样的数,该位填0

⑤应使构造的序列,字典序尽可能大

要求输出对于每一个i,其构造的序列中,非0的元素个数

题解

显然贪心,对于s应找满足和s在原序列里距离不超过k的最大的数t即可,

因为t的序列已经构造好了,所以沿用即可,

用set维护一个[i-k,i+k]的窗口,找到每个i的后继j,统计即可

其实set还是很不熟练,要多敲

注意q.upper_bound(v)比upper_bound(q.begin(),q.end(),v)快很多

用set维护当前[i-k,i+k]的窗口,每次在set中寻找小于a[l]的最大的值,

即去寻找大于-a[l]的最小的值,取反即可

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int T,a[N],n,k,l,r;
int to[N],ans[N];
set<int>q;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
		q.clear();
		q.insert(0);
		l=r=1;
		for(int i=1;i<=n;++i)
		{
			while(r<=n&&r<=i+k)q.insert(-a[r++]);
			while(l<=r&&l<i-k)q.erase(-a[l++]);
			to[a[i]]=-(*q.upper_bound(-a[i])); 
		}
		for(int i=1;i<=n;++i)
		{
			ans[i]=ans[to[i]]+1;
			printf("%d%c",ans[i],i==n?'\n':' ');
		}
	}
	return 0;
}

赛后补题

A.The beautiful values of the palace(找规律+BIT/cdq)

题意

如上,回旋矩阵,左下角(1,1),右上角(n,n),(i,j)位置的价值定义为(i,j)处的数的数位和

T(T<=5)组样例,每次给定矩阵内n(n<=1e6)个点,只有这些点有价值,其余处为0,

m(m<=1e5)次询问,每次给出坐标(x1,y1,x2,y2),询问左下角(x1,y1)右上角(x2,y2)的矩阵中的价值和

题解

先搞出矩阵的规律,然后考虑维护二维前缀和,

树状数组可以直接做,而且更简洁,

把询问处理成四个矩阵,搞二维偏序下的二维前缀和,

这里加了时间一维,也没排序自然就离线了,

刚学的cdq,纯属故意用cdq凑三维偏序(时间,y,x)裸题

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e6+1e5+10;
const int M=1e5+10;
int t,n,m,p,x,y,cnt;
int f,g,c,d;
int ans[M];

int to(int x,int y)
{
	x=x-n/2-1;
	y=y-n/2-1;
	int t=max(abs(x),abs(y));
	ll ans;
	if(x>=y)ans=1ll*n*n-4ll*t*t-2*t-x-y;
	else ans=1ll*n*n-4ll*t*t+2*t+x+y;
	int res=0;
	for(;ans;ans/=10)
	res+=ans%10;
	return res;
}
struct node
{
	int op,x,y,w,id;
	bool operator<(const node &a)
	{
		return y<a.y||(y==a.y&&op<a.op);
	}
}e[N],b[N];

struct BIT
{
	const static int N=1e6+10;
	int n,tr[N];
	void init(int _n)
	{
		n=_n;
		memset(tr,0,sizeof tr);
	}
	void add(int x,int v)
	{
		for(int i=x;i<=n;i+=i&-i)
		tr[i]+=v;
	}
	int sum(int x)
	{
		int sum=0; 
		for(int i=x;i>0;i-=i&-i)
		sum+=tr[i];
		return sum;
	}
}tr;

void cdq(node *a,int l,int r)
{
	if(l==r)return;
	int m=(l+r)/2;
	//分治处理子区间 
	cdq(a,l,m);
	cdq(a,m+1,r);
	//处理左区间对右区间的影响 
	int s=l,t=m+1;
	for(int i=l;i<=r;++i)
	{
		if(t>r||s<=m&&a[s].y<=a[t].y)//左区间的修改 
        //保证y有序 此时x不可能再有序了 插入BIT统计
		{
			//s小于等于 可能还有比t大的 s为t的答案提供贡献 
			//时间序在前 且位置在前(位置相同 先加后统计) 
			if(a[s].op==1)tr.add(a[s].x,a[s].w);
			b[i]=a[s++];
		}
		else//右区间的查询 没有s.b<=t.b的了 统计t的答案 
		{
			//在t位置前的 在该区间内都已被统计 故计算t的答案
			if(a[t].op==2)ans[a[t].id]+=tr.sum(a[t].x);
			else if(a[t].op==3)ans[a[t].id]-=tr.sum(a[t].x);
			b[i]=a[t++];
		}
	} 
    //撤销 清空BIT操作
	for(int i=l;i<=m;++i)
	if(a[i].op==1)tr.add(a[i].x,-a[i].w);
	for(int i=l;i<=r;++i)
	a[i]=b[i];
}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		cnt=0;
		scanf("%d%d%d",&n,&m,&p);
		tr.init(n);
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d",&x,&y);
			e[++cnt]=node{1,x,y,to(x,y),0};
		}
		for(int i=1;i<=p;++i)
		{
			ans[i]=0;
			scanf("%d%d%d%d",&f,&g,&c,&d);
			e[++cnt]=node{2,c,d,0,i};
			e[++cnt]=node{2,f-1,g-1,0,i};
			e[++cnt]=node{3,f-1,d,0,i};
			e[++cnt]=node{3,c,g-1,0,i};
		}
		cdq(e,1,cnt);
		for(int i=1;i<=p;++i)
		printf("%d\n",ans[i]);
	}
	return 0;
} 

 

B.super_log(欧拉降幂)

思路来源

https://www.cnblogs.com/ACMLCZH/p/8117161.html

求a的a的a的...次方(共b个)%m(1<=a<=1e6,0<=b<=1e6,1<=m<=1e6)

主办方出锅了,a不能等于1,log以1为底无意义一直递归

后来告知,a==1输出1%m

 

这题中在gcd(a,p)不等于1时,b可能小于phi[p],

所以与bzoj3884不同,详见cf906D的题解,

所以,学习了一种,在递归的过程中,

不固定加phi[mod],但在求快速幂的过程中多加mod的方法

因此,在调用快速幂过程中,应重写mod函数,整理为欧拉降幂的板子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath> 
using namespace std;
typedef long long ll;
const int maxn=3e6+10; 
bool ok[maxn];
ll prime[maxn],phi[maxn],cnt;
int T,p;
ll a,b,x;
void sieve()
{ 
    phi[1]=1;
	for(ll i=2;i<maxn;++i)
	{
		if(!ok[i])
		{
			prime[cnt++]=i;
			phi[i]=i-1;
		}
		for(int j=0;j<cnt;++j)
		{
			if(i*prime[j]>=maxn)break;
			ok[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
				break; 
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
		}
	}
}
ll Mod(ll x,int mod)
{
	return x<mod?x:x%mod+mod; 
}
ll modpow(ll x,ll n,int mod)
{
	ll ans=1;
	for(;n;n/=2,x=Mod(x*x,mod))
	if(n&1)ans=Mod(ans*x,mod);
	return ans;
}
ll f(ll num,int mod)//要求a^a^a^a..(num个) %mod
{
	if(mod==1)return 1;//a%mod+mod=1
	if(num==1)return Mod(a,mod);
	return modpow(a,f(num-1,phi[mod]),mod);
}
int main()
{
	sieve();
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld%d",&a,&b,&p); 
		if(a==1||b==0)
		{
			printf("%lld\n",1%p);
			continue;
		}
		else if(b==1)
		{
			printf("%lld\n",a%p);
			continue;
		}
		else 
		{
			x=modpow(a,f(b-1,phi[p]),p);
			printf("%lld\n",x%p);
		}
	}
	return 0;
}

C.Tsy's number 5(化简式子+NTT)

题意

T(T<=5)组样例,每次给出一个n(n<=1e5),求上式mod998244353的值

题解

998244353大多和NTT有关,何况这种和式

先把i和j按phi值的贡献将其归位,按官方题解,

第二步化简,考虑i、j的矩阵表,完整矩阵=2*上三角矩阵-主对角线

第三步化简,把i*j搞成2*i*j,再搞成i j i-j独立的式子,

虽然只是完全平方的拆项,但放在NTT这里还是比较不好想,

对根号2做模意义下的二次剩余,for循环跑一下写到常数里

a[]:j序列,j从1起(但0无贡献,所以j从0起无妨),j*fj*根号2的j方次方

b[]:i-j序列,i-j从0起,根号2的0次方,-1次方,-4次方...

卷积只需要通过考虑j的范围,i-j的范围构造数组即可

直接相乘卷积,所得数组第i位,即为i的第二层答案

NTT预处理第二层求和,再O(n)一遍求和

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> 
#include <map>
using namespace std;
typedef long long ll;

const int maxn=1e5+10;
const int sqrt2=116195171;//x*x==2(mod 998244353) 2的二次剩余 
bool ok[maxn];
int prime[maxn],phi[maxn],cnt;
int T,n,f[maxn],invsqrt2;

const int mod=998244353;
const int G=3;
ll inv,N;
ll ans,rev[maxn*3],back[maxn*3];
ll a[maxn*3],b[maxn*3],c[maxn*3],d[maxn*3];

void sieve()
{ 
    phi[1]=1;
	for(ll i=2;i<maxn;++i)
	{
		if(!ok[i])
		{
			prime[cnt++]=i;
			phi[i]=i-1;
		}
		for(int j=0;j<cnt;++j)
		{
			if(i*prime[j]>=maxn)break;
			ok[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
				break; 
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
		}
	}
}
void getf(int n)
{
	memset(f,0,sizeof f);
	for(int i=1;i<=n;++i)
	f[phi[i]]++; 
}
ll power(ll x,ll y)
{
    ll res=1ll;
    for(;y;y>>=1)
    {
        if(y&1)res=res*x%mod;
        x=x*x%mod;
    }
    return res;
}
void init(int n,int m)
{
	int len=0;
    while((n+m+2)>=(1<<len))len++;
    N=(1<<len);
    inv=power(N,mod-2);
    for(int i=0;i<N;i++)
    {
        ll pos=0;
        ll temp=i;
        for(int j=1;j<=len;j++)
        {
            pos<<=1;pos |= temp&1;temp>>=1;
        }
        back[i]=rev[i]=pos;
    }
}
void init2()
{
	for(int i=0;i<N;++i)
	rev[i]=back[i];
}
void ntt(ll *a,ll n,ll re)
{
    for(int i=0;i<n;i++)
    {
        if(rev[i]>i)
        {
            swap(a[i],a[rev[i]]);
        }
    }
    for(int i=2;i<=n;i<<=1)
    {
        ll mid=i>>1;
        ll wn=power(G,(mod-1)/i);
        if(re) wn=power(wn,(mod-2));
        for(int j=0;j<n;j+=i)
        {
            ll w=1;
            for(int k=0;k<mid;k++)
            {
                ll temp1=a[j+k];
                ll temp2=a[j+k+mid]*w%mod;
                a[j+k]=(temp1+temp2)%mod;
                a[j+k+mid]=(temp1-temp2);
				a[j+k+mid]=(a[j+k+mid]%mod+mod)%mod;
                w=w*wn%mod;
            }
        }
    }
    if(re)
    {
        for(int i=0;i<n;i++)
        {
            a[i]=(a[i]*inv)%mod;
        }
    }
}
void solve2(ll len1,ll len2)
{
	init2();
	for(int i=0;i<N;++i)
	a[i]=b[i]=0;
	for(int i=0;i<len1;++i)
	{
		ll x=1ll*i*i,y=i*f[i]%mod;
		a[i]=y*power(sqrt2,x)%mod;
		c[i]=a[i];
		d[i]=y*y%mod*power(2,x)%mod;
	}
	for(int i=0;i<len2;++i)
	b[i]=power(invsqrt2,1ll*i*i);
    ntt(a,N,0);
    ntt(b,N,0);
    for(int i=0;i<N;++i)
    a[i]=a[i]*b[i]%mod;
    ntt(a,N,1);
}
int main()
{
	invsqrt2=power(sqrt2,mod-2);
	sieve();
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		getf(n);
		init(n+1,n+1);
		solve2(n+1,n+1);
		ans=0;
		for(int i=1;i<=n;++i)
		ans=(ans+2*c[i]*a[i]-d[i]+mod)%mod;
		printf("%lld\n",ans);
	}
	return 0;
}

D. Robots(概率dp)

题意

n(n<=1e5)个点,m(m<=2e5)条边,保证是有向拓扑图,

只有一个点入度为0,一个点出度为0,

一个机器人从1点出发,每天要么转向一个邻接节点要么保持原地不动,所有可能都是等概率的

特别地,它这一天的cost等于已经经历过的天数

问从起点1到终点n的cost期望

题解

令t[i]代表i到n的期望时间,则t[i]=\frac{1}{deg[i]+1}(t[i]+\sum t[next[i]])+1

其中,deg[i]是i的后继的个数,next[i]是i的后继,

也就是说无论走回自己还是走到其它顶点都需要额外花1天

预处理拓扑图,移项化简然后dp

令dp[i]代表i到n的期望代价,则dp[i]=\frac{1}{deg[i]+1}(dp[i]+\sum dp[next[i]])+t[i]

为什么是t[i],可以倒着从n出发来考虑,从n到i需要t[i]天,

那么从i出发的时候已经经历了t[i]天,此时代价为t[i],

也就是说无论走回自己还是走到其它顶点都需要额外花t[i]的代价

dp[1]即为所求,即两次都倒着考虑

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn=1e5+10;
int T,n,m,a,b,cnt,sum;
int in[maxn],c[maxn];
double t[maxn],dp[maxn];
//t[i]:i->n的期望时间 
//dp[i]:i->n的期望代价 
vector<int>e[maxn];
queue<int>q;
bool vis[maxn];
void topo()
{
	for(int i=1;i<=n;++i)
	{
		if(!in[i])
		{
			q.push(i);
			vis[i]=1; 
		} 
	}
	while(!q.empty())
	{
		int t=q.front(); 
		q.pop();
		c[++cnt]=t;
		int len=e[t].size();
		for(int i=0;i<len;++i)
		{
			if((--in[e[t][i]])==0)
			q.push(e[t][i]);
		}
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		cnt=0;
		for(int i=1;i<=n;++i)
		{
			e[i].clear();
			t[i]=dp[i]=in[i]=0;
		}
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d",&a,&b);
			e[a].push_back(b);in[b]++;
		}
		topo();
		for(int i=n-1;i>=1;--i)
		{
			sum=e[c[i]].size()+1;
			for(int j=0;j<e[c[i]].size();++j)
			t[c[i]]+=t[e[c[i]][j]];
			t[c[i]]=(t[c[i]]+sum)/(1.0*(sum-1));
		}
		for(int i=n-1;i>=1;--i)
		{
			sum=e[c[i]].size()+1;
			for(int j=0;j<e[c[i]].size();++j)
			dp[c[i]]+=dp[e[c[i]][j]];
			dp[c[i]]=(dp[c[i]]+1.0*sum*t[c[i]])/(1.0*(sum-1));
		}
		printf("%.2lf\n",dp[1]);
	} 
	return 0;
} 

E.K Sum(杜教筛+莫比乌斯反演+欧拉定理+等比数列求和)

思路来源:https://blog.youkuaiyun.com/qq_30974369/article/details/79087445

题意

样例数T<=10,n<=1e9,k<=1e(1e5),

思路来源

https://blog.youkuaiyun.com/qq_30974369/article/details/79087445 

题解

杜教筛 多自己手推推

先反演,弄出两个的时候的情况,

多个的情况显然就是等比数列求和,注意特判公比为1的情形,

化简不动的时候,注意枚举i*d=S中的S,

如果能快速地求图片中g(n),外层数论分块的复杂度就是O(\sqrt n)

而g(n)显然积性函数,可以通过前面2e6用积性函数线性筛,

后面用杜教筛来完成,注意到g=(id*id)卷积mu,

g卷积1=(id*id)卷积mu卷积1=(id*id)卷积e=id*id

g(1)S(n)=\sum_{i=1}^{n}(g*1)(i)=\sum_{i=1}^{n}i^{2}-\sum_{i=2}^{n}g(i)S([\frac{n}{i}])

其实,也就是mu常配1而已,但不要被套路套死,

代入g(1)=1即可,余则用杜教筛O(n^{\frac{2}{3}})完成,

考虑到分块要求的[l,r]=djs(r)-djs(l-1),而杜教筛也是分块实现的,

二者分块的是相同的端点,所以求所有端点的复杂度,

相当于只求一遍n的杜教筛,复杂度只有O(n^{\frac{2}{3}})而不是O(n^\frac{2}{3}*\sqrt{n})

 

感谢cometoj群 数论只会骗子 大佬耐心详细的解答 右下角那个分母是6

代码

复杂度由于杜教筛和分块的端点重合,所以杜教筛的复杂度只有O(n^{\frac{2}{3}})

分块套快速幂,总复杂度O(n^{\frac{2}{3}}+\sqrt{n}*log(10^{9}))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e7+10;
const int inv6=(mod+1)/6;
const int N=1e5+10;
bool ok[maxn];
int prime[maxn],cnt;
int low[maxn],lowp[maxn];
ll f[maxn];
int T,n,len,x;
ll res,v;
char s[N];
map<int,ll>ff;
void sieve()
{
    f[1]=1;
    for(ll i=2;i<maxn;++i)
    {
        if(!ok[i])
        {
            prime[cnt++]=i;
            low[i] = lowp[i] = i;
        	f[i] =(i*i-1)%mod;/*i为质数的情况f(p)*/ 
        }
        for(int j=0;j<cnt;++j)
        {
            ll k=i*prime[j];
            if(k>=maxn)break;
            ok[k]=1;
            if(i%prime[j]==0)//i中出现过prime[j] 
            {
            	low[k]=prime[j];
            	lowp[k]=lowp[i]*prime[j];
            	if(i==lowp[i])f[k]=f[i]*(f[prime[j]]+1)%mod;/*i中全为prime[j] f(p^k)的情况*/
            	else f[k]=f[i/lowp[i]]*f[lowp[i]*prime[j]]%mod;/*i中不全为prime[j] 将最小素因子prime[j]和其他分开 显然互质*/
                break; 
            }
            else
            {
             low[k]=lowp[k]=prime[j];
             f[k]=f[i]*f[prime[j]]%mod;//i中没出现过prime[j] i与prime[j]互质 
            }
        }
    }
    for(int i=2;i<maxn;++i)
    {
    	f[i]=(f[i]+f[i-1])%mod; 
    }
}
ll modpow(ll x,ll n,ll mod)
{
	ll res=1;
	for(;n;n/=2,x=x*x%mod)
	if(n&1)res=res*x%mod;
	return res;
}
ll cal(ll x)
{
	ll inv=modpow(x-1,mod-2,mod);
	ll y=modpow(x,v,mod);
	ll z=modpow(x,2,mod);
	return (y-z+mod)%mod*inv%mod;
}
ll getf(int n)
{
	if(n<maxn)return f[n];
	if(ff.count(n))return ff[n];
	ll ans=1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod;
	for(int l=2,r;l<=n;l=r+1)
	{
		r=n/(n/l);
		ans=(ans+mod-1ll*(r-l+1)*getf(n/l)%mod)%mod;
	} 
	return ff[n]=ans;
}
ll solve(int n)
{
	ll ans=0;
    for(int l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        if(r==n)ans=(ans+(getf(r)-getf(l-1)+mod)%mod*(res-1+mod)%mod)%mod;
        else ans=(ans+(getf(r)-getf(l-1)+mod)%mod*cal(n/l)%mod)%mod;
    }
    return ans;
}
int main()
{
    sieve();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%s",&n,s);
        len=strlen(s);
        res=0;v=0;
        for(int i=0;i<len;++i)
        {
			res=(res*10+(s[i]-'0'))%mod;
			v=(v*10+(s[i]-'0'))%(mod-1);
		}
		v=(v+1)%(mod-1);
        printf("%lld\n",solve(n));
    }
    return 0;
} 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值