中山纪中集训Day7+8.7模拟赛题解

Part.I 游记

又是爆炸的一天。。。

今天上午10点45左右收到了来自WLS的QQ。。。(然而我并没有收到QAQ…我下午才发现。。。

今天第一题的转移就像一个矩乘的题,然而我连矩阵都没有构造出来。。。然后就只有30的暴力分了。。。

第二题看上去就是个二分图匹配,然后我就愉快的打了个匈牙利。。。然后居然RE了。。。(我写得太丑了。。。(考完发现它就是贪心。。。

第三题最后才发现至少能骗90分。。。然而我没有时间去打了。。

Part.II 题解

A.小L的数列

题目

题目描述

输入

一行两个整数n和k。

之后1行k个正整数b1…bk。

之后1行k个正整数f1…fk。

输出

输出一个整数表示fn

样例输入

【样例输入1】
5 4
1 2 3 4
4 3 2 1
【样例输入2】
100000 4
1 2 3 4
12 23 34 45

样例输出

【样例输出1】
27648
【样例输出2】
33508797

数据范围

对于30%的数据,n≤10000.

对于另外20%的数据,bi=1,n≤1000000.

对于另外20%的数据,f[1]…f[k-1]=1.

对于另外20%的数据,k≤30.

对于100%的数据,1≤k≤200,1≤n≤40000000,1≤bi,fi≤998244352.

提示

样例解释:122333444*4=27648

分析

不难发现每步转移的实质是相同的,所以我们考虑利用矩阵乘法解决这道题,因为幂的乘法就相当于指数的加法,幂的乘方就相当于指数的乘法。

对于任意一个数 f i ( i ≥ k ) f_i(i\ge k) fi(ik),我们都可以将它表示成 f 1 a 1 f 2 a 2 ⋯ f k a k f_1^{a_1}f_2^{a_2}\cdots f_k^{a_k} f1a1f2a2fkak,所以我们只求出它的每个数的指数就可以了。

对于一个数 f j f_j fj,当它处在第 i i i个位置时,它的指数就会乘上 b i b_i bi。所以我们可以构造一个对角线为 1 1 1,第一列为 b 1 , b 2 , … , b k b_1,b_2,\ldots,b_k b1,b2,,bk的矩阵,对于每个数做快速幂即可。

这样时间复杂度显然是过不去的。不难发现对于每个数,所构造出的矩阵都是一样的,所以我们可以先预处理出这个矩阵,最后统计答案即可。

参考代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int Maxk=200;
const int Mod=998244353;

int N,K,B[Maxk+5],f[Maxk+5];

struct Matrix {
	int a[Maxk+5][Maxk+5];
};
Matrix operator * (Matrix &lhs,Matrix &rhs) {
	Matrix ret;
	memset(ret.a,0,sizeof ret.a);
	for(int i=1;i<=K;i++)
		for(int j=1;j<=K;j++)
			for(int k=1;k<=K;k++)
				ret.a[i][k]=(ret.a[i][k]+1LL*lhs.a[i][j]*rhs.a[j][k]%(Mod-1))%(Mod-1);
	return ret;
}

inline int QuickPow(int a,int k) {
	int ret=1;
	while(k) {
		if(k&1)ret=(1LL*ret*a)%Mod;
		a=1LL*a*a%Mod;
		k>>=1;
	}
	return ret;
}
inline Matrix QuickPow(Matrix t,int k) {
	Matrix ret;
	memset(ret.a,0,sizeof ret);
	for(int i=1;i<=K;i++)
		ret.a[i][i]=1;
	while(k) {
		if(k&1)ret=ret*t;
		t=t*t;
		k>>=1;
	}
	return ret;
}

int main() {
//	#ifdef LOACL
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
//	#endif
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout); 
	scanf("%d %d",&N,&K);
	for(int i=1;i<=K;i++)
		scanf("%d",&B[i]);
	for(int i=1;i<=K;i++)
		scanf("%d",&f[i]);
	if(N<=K) {
		printf("%d\n",f[N]);
		return 0;
	}
	Matrix res;
	res.a[1][1]=B[1];
	for(int i=2;i<=K;i++)
		res.a[i][i-1]=1,res.a[1][i]=B[i];
	res=QuickPow(res,N-K);
	int ans=1;
	for(int i=1;i<=K;i++)
		ans=1LL*ans*QuickPow(f[K-i+1],res.a[1][i])%Mod;
	printf("%d\n",ans);
	return 0;
}

B.梦境

题目

题目描述

输入

输出

样例输入

2 2
1 3
2 4
1
3

样例输出

2

数据范围

分析

这道题不难看出是个二分图最大匹配问题,可以用二分图+网络流骗到70分(网络流的复杂度其实是跑不满的。

那么对于100分的数据,不难发现难点其实是在建图上。不难发现将转折点按时间顺序排好后,对于一个梦境,它其实是对应了一段连续的转折点,所以我们可以采用线段树优化,最后再跑一跑ISAP就可以了。(这样似乎就可以拿到100分了。。。

考虑贪心,对于一个转折点,我们选取左端点离它最近的点与它配对,用优先队列维护即可(正确性我不会证QAQ。。。

参考代码

贪心
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;

const int Maxn=200000;

int N,M;
struct Node {
	int l,r;
	bool operator < (const Node &rhs) const {return l==rhs.l?r<rhs.r:l<rhs.l;}
};

int T[Maxn+5];
Node A[Maxn+5];

int main() {
//	#ifdef LOACL
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
//	#endif
	freopen("dream.in","r",stdin);
	freopen("dream.out","w",stdout);
	scanf("%d %d",&N,&M);
	for(int i=1;i<=N;i++)
		scanf("%d %d",&A[i].l,&A[i].r);
	for(int i=1;i<=M;i++)
		scanf("%d",&T[i]);
	sort(A+1,A+N+1),sort(T+1,T+M+1);
	priority_queue<int,vector<int>,greater<int> > q;
	int ans=0;
	for(int i=1,j=1;i<=M;i++) {
		while(j<=N&&A[j].l<=T[i])
			q.push(A[j].r),j++;
		while(!q.empty()&&q.top()<T[i])
			q.pop();
		if(!q.empty()&&q.top()>=T[i])
			ans++,q.pop();
	}
	printf("%d\n",ans);
	return 0;
}
线段树优化建图网络流

待填坑

C.树

题目

题目描述

有一棵n个节点的无根树,给出其中的m对点对<x,y>。问有多少条树上的简单路径<u,v>满足该路径上不存在任何一对给出的点对<x,y>。

这里我们认为路径<u,v>和<v,u>是相同的。并且对于题目中给出的点对<x,y>满足x!=y,对于你要计数的路径<u,v>满足u!=v(即单点不算答案)。

输入

第一行两个正整数n,m。
接下来n-1行每行两个正整数u,v描述树上的一条边。
接下来m行每行两个正整数x,y描述一对给出的点对。
(注意,这里我们不保证同样的点对<x,y>不会重复出现)

输出

一行一个整数,表示满足要求的树上简单路径的条数。

样例输入

8 3
1 2
1 3
4 8
2 4
2 5
3 6
3 7
2 3
4 8
6 7

样例输出

11

数据范围

样例解释

满足条件的路径为<1,2>,< 1,3 >,< 1,4 >,< 1,5 >,< 1,6 >,< 1,7 >,< 2,4 >,< 2,5 >,< 3,6 >,< 3,7 >,< 4,5 >。

分析

我们发现直接计算合法路径似乎有点困难,所以我们考虑计算不合法路径。

对于菊花图的情况,不难发现只要有了点对< 1,u >,就相当于把u删了,有了点对< u,v >,就相当于多了一个不合法的路径。

对于一条链,一个限制< u,v >就相当于在这个DFS序列上,不合法的路径的一端落在 [ 1 , u ] [1,u] [1,u],另一端落在 [ v , N ] [v,N] [v,N]的一个序列,则问题转化为求不合法方案数的并集。若我们把 [ 1 , u ] [1,u] [1,u]看做平行于 x x x轴上的一条线段, [ v , N ] [v,N] [v,N]看做平行于 y y y轴的线段,这些不合法的点对数量就是这个矩形的面积。这是个二维的限制,我们就可以利用扫描线+线段树解决这个问题。

那么我们考虑用DFS序解决掉这个问题。若两个节点不是祖先-后代的关系,那么不合法的路径的起点和终点必须分别在两棵子树内部。否则若 u u u为祖先, v v v为后代,那么不合法路径的起点和终点分别是 v v v子树内部节点和 u u u子树中除 v v v子树的其他节点。

接下来套用链上的做法即可。

参考代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

const int Maxn=100000;
const int Maxlogn=18;

int N,M;

struct SegmentTree {
	struct Segment {
		int val,tag;
	};
	Segment t[Maxn*8+5];
	void modify(int rt,int l,int r,int ml,int mr,int val) {
		if(ml<=l&&r<=mr) {
			t[rt].tag+=val;
			if(t[rt].tag)t[rt].val=r-l+1;
			else if(l==r)t[rt].val=0;
			else t[rt].val=t[rt<<1].val+t[rt<<1|1].val;
			return;
		}
		int mid=(l+r)>>1;
		if(ml<=mid)modify(rt<<1,l,mid,ml,mr,val);
		if(mr>=mid+1)modify(rt<<1|1,mid+1,r,ml,mr,val);
		if(t[rt].tag)t[rt].val=r-l+1;
		else if(l==r)t[rt].val=0;
		else t[rt].val=t[rt<<1].val+t[rt<<1|1].val;
	}
};
SegmentTree tr;

struct Matrix {
	int line,y1,y2;
	int val;
	bool operator < (const Matrix &rhs) const {return line<rhs.line;}
};
vector<Matrix> t;
void add_matrix(int x1,int x2,int y1,int y2) {
	t.push_back(Matrix{x1,y1,y2,1});
	t.push_back(Matrix{x2+1,y1,y2,-1});
}

struct Edge {
	int to;
	Edge *nxt;
};
Edge pool[Maxn*2+5];
Edge *ecnt=&pool[0];
Edge *G[Maxn+5];
void addedge(int u,int v) {
	Edge *p=++ecnt;
	p->to=v;
	p->nxt=G[u],G[u]=p;
}
int dep[Maxn+5],fa[Maxn+5][Maxlogn+5];
int dfn[Maxn+5],fir[Maxn+5],las[Maxn+5],cnt;
void dfs(int u,int f,int depth) {
	dfn[++cnt]=u,fir[u]=cnt;
	dep[u]=depth,fa[u][0]=f;
	for(int i=1;i<=Maxlogn;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(Edge *p=G[u];p!=NULL;p=p->nxt) {
		int v=p->to;
		if(v==f)continue;
		dfs(v,u,depth+1);
	}
	las[u]=cnt;
}
int calc_lca(int u,int v) {
	for(int i=Maxlogn;i>=0;i--)
		if(dep[fa[v][i]]>dep[u])
			v=fa[v][i];
	return v;
}

int main() {
//	#ifdef LOACL
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
//	#endif
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d %d",&N,&M);
	for(int i=1;i<N;i++) { 
		int u,v;
		scanf("%d %d",&u,&v);
		addedge(u,v);
		addedge(v,u);
	}
	dfs(1,-1,1);
	for(int i=1;i<=M;i++) {
		int u,v;
		scanf("%d %d",&u,&v);
		if(fir[u]>fir[v])swap(u,v);
		if(fir[v]<=las[u]&&fir[v]>fir[u]) {
			int lca=calc_lca(u,v);
			if(fir[lca]>1)add_matrix(1,fir[lca]-1,fir[v],las[v]);
			if(las[lca]<N)add_matrix(fir[v],las[v],las[lca]+1,N);
		} else add_matrix(fir[u],las[u],fir[v],las[v]);
	}
	sort(t.begin(),t.end());
	long long ans=1LL*N*(N-1)/2;
	for(int i=1,j=0;i<=N;i++) {
		while(j<(int)t.size()&&t[j].line<=i)
			tr.modify(1,1,N,t[j].y1,t[j].y2,t[j].val),j++;
		ans-=tr.t[1].val;
	}
	printf("%lld\n",ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值