2019 南昌网络赛(The 2019 Asia Nanchang)B、C(线段树维护转移矩阵)、D(母函数+分治FFT)、E、G、H(分块优化矩阵块速幂)、I题待补、D题待补、A题待补

赛中通过

B. Fire-Fighting Hero(最短路/多源bfs 签到)

题意

比较多源bfs的最短路和单源最短路哪个大

题解

可以对多源建个虚点,跑两次单源最短路

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define pb push_back
#define fi first
#define se second
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const int N=1e3+5;
vector<P>E[N];
vector<int>now,hero;
int d[N],ans1,ans2;
int t,n,m,s,k,c,u,v,w;
int bfs(vector<int> &now)
{
	int res=0;
	memset(d,INF,sizeof d);
	priority_queue<P,vector<P>,greater<P> >q;
	for(int i=0;i<now.size();++i)
	{
		int v=now[i];
		d[v]=0;q.push(P(0,v));
	}
	while(!q.empty())
	{
		int dis=q.top().first,u=q.top().second;
		q.pop();
		if(d[u]<dis)continue;
		int len=E[u].size();
		for(int i=0;i<len;++i)
		{
			int v=E[u][i].fi;
			int w=E[u][i].se;
			if(d[v]>d[u]+w)
			{
				d[v]=d[u]+w;
				q.push(P(d[v],v));
			}
		}	
	} 
	for(int i=1;i<=n;++i)
	res=max(res,d[i]);
	return res;
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d%d%d",&n,&m,&s,&k,&c);
		for(int i=1;i<=n;++i)
		E[i].clear();
		hero.clear();
		hero.pb(s);
		now.clear();
		for(int i=1;i<=k;++i)
		{
			scanf("%d",&v);
			now.pb(v);
		}
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			E[u].pb(P(v,w));
			E[v].pb(P(u,w));
		}
		ans1=bfs(hero);
		ans2=bfs(now);
		if(1ll*ans1<=1ll*ans2*c)printf("%d\n",ans1);
		else printf("%d\n",ans2);
	}
	return 0;
}

E. Magic Master(约瑟夫环模拟)

约瑟夫环模拟复杂度O(T*n*m),n为圈长,m为每次跳的步数,常数小所以能过

#include<bits/stdc++.h>
using namespace std;
const int N=4e7+10;
int t,n,m,q;
int nex[N],ans[N],cnt;
int now,tmp,k;
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		//0到n-1 
		cnt=0;now=1;
		for(int i=1;i<n;++i)nex[i]=i+1;
		nex[n]=1;
		ans[now]=++cnt;
		nex[n]=2;
		for(int j=1;j<n;++j)
		{
			for(int i=1;i<=m;++i)
			now=nex[now];
			tmp=nex[now];
			ans[tmp]=++cnt;
			nex[now]=nex[tmp];
		}
		scanf("%d",&q);
		for(int j=1;j<=q;++j)
		{
			scanf("%d",&k);
			printf("%d\n",ans[k]);
		}
	}
	return 0;
}

G.过于签到 不配拥有姓名

赛后补题

C.Hello 2019(线段树维护转移矩阵)

题意

长度为n(n<=2e5)的数字串,q(q<=2e5)个询问,

每次询问区间[l,r]内,最少删去多少个字符,

使得区间含9102子序列且不含8102子序列,

不能实现输出-1

题解

goodbye2016原题,就改了一下顺序,

先把串反过来,等价于含2019且不含2018子序列,

线段树,每个位置维护一个5*5矩阵,代表5种状态

0状态:空串 1状态:子序列2 2状态:子序列20 

3状态:子序列201 4状态:子序列2019

矩阵中c[i][j]定义为从i状态转移到j状态所要删去的最少的字符数

初始时,c[i][j]=INF(i!=j),c[i][j]=0(i==j),

每加入一个字符p使得i能转移到i+1状态时,

c[i][i+1]=0,c[i][i]=1(此时想要保持原状态不变只能删了p)

特别地,当加入8进来时,

201无法再保持201,2019也无法保持2019(因为必后缀8使得产生子序列2018,必删8),

所以,有c[3][3]=c[4][4]=1,

重载矩阵乘法,通过枚举k,维护从i状态到j状态转移删去字符最小值

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
const int INF=0x3f3f3f3f;
int n,q,l,r,v;
char a[N];
struct mat
{
	const static int MAXN = 5;
    int c[MAXN][MAXN];
    int m, n;
    mat(){
    	memset(c,0,sizeof (c));
    }
    mat(int a, int b) : m(a), n(b) {
        memset(c, 0, sizeof (c));
    }
    void reset(int a,int b){
    	m=a; n=b;
    }
    void clear(){
		memset(c, 0, sizeof(c)); 
    }
    mat operator + (const mat& temp) {
        mat ans(m, temp.n);
        for (int i = 0; i < m; i ++)
            for (int j = 0; j < temp.n; j ++)
            {
				ans.c[i][j] = INF;
                for (int k = 0; k < n; k ++)
                ans.c[i][j] = min(ans.c[i][j] , c[i][k] + temp.c[k][j]);
            }
        return ans;
    }
}dat[N*4],ans;
void pushup(int p)
{
	dat[p]=dat[p<<1]+dat[p<<1|1];
}
void init(int p,int l,int r)
{
	for(int i=0;i<dat[p].n;++i)
	{
		for(int j=0;j<dat[p].m;++j)
		{
			dat[p].c[i][j]=(i==j)?0:INF;
		}
	}
	if(a[l]=='2')dat[p].c[0][1]=0,dat[p].c[0][0]=1;//空 后者1说明不转不行 不转必须删 
	if(a[l]=='0')dat[p].c[1][2]=0,dat[p].c[1][1]=1;//2
	if(a[l]=='1')dat[p].c[2][3]=0,dat[p].c[2][2]=1;//20
	if(a[l]=='9')dat[p].c[3][4]=0,dat[p].c[3][3]=1;//201
	if(a[l]=='8')dat[p].c[3][3]=1,dat[p].c[4][4]=1;//2019 201->201必删此8 
}
void build(int p,int l,int r)//每一行进行操作 
{
	dat[p].reset(5,5);
	if(l==r)
	{
		init(p,l,r);
		return;
	}
	int mid=(l+r)/2;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
mat ask(int p,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)return dat[p];
	int mid=(l+r)/2;
	if(ql>mid)return ask(p<<1|1,mid+1,r,ql,qr);
	if(qr<=mid)return ask(p<<1,l,mid,ql,qr);
	return ask(p<<1,l,mid,ql,mid)+ask(p<<1|1,mid+1,r,mid+1,qr);
}
int main()
{
	scanf("%d%d",&n,&q);
	scanf("%s",a+1);
	reverse(a+1,a+n+1);
	build(1,1,n);
	while(q--)
	{
		scanf("%d%d",&l,&r);
		l=n+1-l;r=n+1-r;swap(l,r);
		ans=ask(1,1,n,l,r);
		v=ans.c[0][4];
		printf("%d\n",v==INF?-1:v);
	}
	return 0;
}

D.Interesting Series(母函数+分治FFT)

题目

n,q<=1e5,2<=a<=1e3,1<=si<=1e9

F(1)=1,F(n)=a*F(n-1)+1,

n个数,第i个数为si,

一个集合s的价值v(s),定义为F(sum),其中sum为该集合内所有元素和

对每个子集内元素个数相同的子集,将其价值再求和,作为该元素个数下的答案

q次询问,每次询问一个k,输出对于该子集元素个数k下的答案%(1e6+3)

题解

考虑F(n),写成等比数列求和形式,F(n)=\frac{1-a^n}{1-a}

构造生成函数如下,

对于每个括号内都二选一,对于选到了一些a^{s_{i}}的集合,

不妨计集合内元素个数为k个,和为s_{i}+...\dot+s_{j}

其在生成函数中的乘积可表示为a^{s_{i}+...\dot+s_{j}}*x^{n-k},一个集合的贡献应为\frac{1-a^{s_{i}+...\dot+s_{j}}}{1-a}

分治FFT求出x^{n-k}的系数,共有C(n,k)个集合对其起到贡献,

故对于一个询问k,其答案为\frac{C(n,k)-coefficient\ of\ x^{n-k}}{1-a}

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1<<18,mod=1e5+3;
double PI=acos(-1.0);

int n,a,q,k;
int c[N],s[N];

struct C{
	double r,i;
	C(){}
	C(double a,double b){r=a,i=b;}
	C operator + (C x){return C(r+x.r,i+x.i);}
	C operator - (C x){return C(r-x.r,i-x.i);}
	C operator * (C x){return C(r*x.r-i*x.i,r*x.i+i*x.r);}
}w[N],A[N],B[N];
int R[N];
vector <int> colors[N<<1];
struct cmp{
	bool operator ()(int a,int b){
		return colors[a].size()>colors[b].size();
	}
};
priority_queue <int,vector<int>,cmp> heap;
void FFT(C a[],int n){
	for (int i=0;i<n;i++)
		if (i<R[i])
			swap(a[i],a[R[i]]);
	for (int t=n>>1,d=1;d<n;d<<=1,t>>=1)
		for (int i=0;i<n;i+=(d<<1))
			for (int j=0;j<d;j++){
				C tmp=w[t*j]*a[i+j+d];
				a[i+j+d]=a[i+j]-tmp;
				a[i+j]=a[i+j]+tmp;
			}
}
void FFT_times(vector <int> &a,vector <int> &b,vector <int> &c){
	int n,d;
	for (int i=0;i<a.size();i++)
		A[i]=C(a[i],0);
	for (int i=0;i<b.size();i++)
		B[i]=C(b[i],0);
	for (n=1,d=0;n<a.size()+b.size()-1;n<<=1,d++);
	for (int i=0;i<n;i++){
		R[i]=(R[i>>1]>>1)|((i&1)<<(d-1));
		w[i]=C(cos(2*PI*i/n),sin(2*PI*i/n));
	}
	for (int i=a.size();i<n;i++)
		A[i]=C(0,0);
	for (int i=b.size();i<n;i++)
		B[i]=C(0,0);
	FFT(A,n),FFT(B,n);
	for (int i=0;i<n;i++)
		A[i]=A[i]*B[i],w[i].i*=-1.0;
	FFT(A,n);
	c.clear();
	for (int i=0;i<=a.size()+b.size()-2;i++)
		c.push_back(((LL)(A[i].r/n+0.5))%mod);
}
int modpow(int x,int n,int mod)
{
	int res=1;
	for(;n;n>>=1,x=1ll*x*x%mod)
	if(n&1)res=1ll*res*x%mod;
	return res;
}
int main(){
	scanf("%d%d%d",&n,&a,&q);
	for(int i=1;i<=n;++i)
	scanf("%d",&s[i]);
	while (!heap.empty())
		heap.pop();
	int sz=0;
	for (int i=1;i<=n;i++){
		colors[++sz].clear();
		colors[sz].push_back(modpow(a,s[i],mod));
		colors[sz].push_back(1);
		heap.push(sz);
	}
	while (heap.size()>=2){
		int x=heap.top();
		heap.pop();
		int y=heap.top();
		heap.pop();
		FFT_times(colors[x],colors[y],colors[++sz]);
		colors[x].clear(),colors[y].clear();
		heap.push(sz);
	}
	c[0]=1;//C(n,0)
	for(int i=1;i<=n;++i)
	c[i]=1ll*c[i-1]*(n-i+1)%mod*modpow(i,mod-2,mod)%mod;
	int inv=modpow(1-a+mod,mod-2,mod);
	for(int i=1;i<=q;++i)
	{
		scanf("%d",&k);
		int res=(c[k]-colors[sz][n-k]+mod)%mod;
		res=1ll*res*inv%mod;
		printf("%d\n",res);
	}
	return 0;
}

H.The Nth Item(分块优化矩阵块速幂)

有个很类似的题目 洛谷P5110 块速递推

题意

给定一个递推式f[0]=0,f[1]=1,f[i]=3*f[i-1]+2*f[i-2],

询问f[n](0<=n<=1e18)%998244353,强制在线,询问q(q<=1e7)次

题解

打表得,该式循环节cyc为(mod-1)/2,mod取998244353

所以,问题转化为询问一个f[n](0<=n<cyc)的值,n在1e9以内,

像bsgs一样打表,块内每个单独打,块外按块号整个打表,

块的大小,取sqrt(n)即可,空间开2*sqrt(n)即可存下

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int inv2=(mod+1)/2;
const int cyc=(mod-1)/2;
const int m17=473844410;//sqrt(17) 二次剩余 
const int im17=438914993;//1/sqrt(17) m17^(mod-2)
typedef long long ll;
int q,ans;
ll n,res;
struct POW
{
	const static int N=1e5;
	int v,high[N+2],low[N+2];
	void init(int x)
	{
		v=x;
		low[0]=1;
		for(int i=1;i<=N;++i)
		low[i]=(1ll*low[i-1]*v)%mod;
		high[0]=1;
		for(int i=1;i<=N;++i)
		high[i]=(1ll*high[i-1]*low[N])%mod;
	}
	int solve(int x)
	{
		return 1ll*high[x/N]*low[x%N]%mod; 
	}
}a,b;
int main()
{
	a.init(1ll*(3+m17)*inv2%mod);
	b.init((1ll*(3-m17)*inv2%mod+mod)%mod);
	scanf("%d%lld",&q,&n);
	while(q--)
	{
		ans=1ll*(a.solve(n%cyc)-b.solve(n%cyc)+mod)*im17%mod;
		res^=ans;
		n=n^(1ll*ans*ans);
	}
	printf("%lld\n",res);
	return 0;
} 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Code92007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值