GDCPC广东省大学生程序设计竞赛

比赛链接

GDCPC广东省大学生程序设计竞赛

A.An Easy Problem

给你三个数n,m,k
然后会根据n,m生成数列
1×1,1×2,······,1×m
2×1,2×2,······,2×m
···
n×1,n×2,······,n×m
问你将这些数字中第k大的数字是多少

解题思路

二分

AcWing 4080. 第k个数

  • 时间复杂度:\(O(n+m)log(nm)\)

大根堆

\((1\times m,1),(2\times m,2),(3\times m,3),\dots,(n\times m,n)\) 放入大根堆中,每个二元组负责 \(m\) 个数,这样所有的数都能兼顾到,且初始时数最大,如 \((n\times m,n)\) 负责 \(n\times 1,n\times 2,\dots,n\times m\) 这些数,当前最大数要求在堆中,将最大数 \((i\times j)\) 弹出,同时将 \((i\times j -i,i)\) 压入堆中,此时最大值肯定仍在堆中,弹出 \(k-1\) 次即得 \(k\) 大数
注意 \(k\) 不会很大,数列中实际上会有很多重复数

  • 时间复杂度:\(O(nlogn+klogn)\)

代码

// Problem: An Easy Problem
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/A
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

int n,m,k;
priority_queue<pair<LL,int>> q;
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)q.push({1ll*i*m,i});
    while(--k)
    {
    	auto t=q.top();
    	q.pop();
    	q.push({t.fi-t.se,t.se});
    }
    cout<<q.top().fi;
    return 0;
}

B.Byfibonacci

\(T\) 组询问,求出 \(w(F(x))\),用不同斐波那契数构成的 \(x\) 的方案的价值的和,方案的价值为每个斐波那契数的乘积

解题思路

dp

首先有斐波那契数的一个前缀和性质:\(s[x]=f[x+2]+1\),故大概对于任何一个数 \(x\),其由不同斐波那契数组成的数的最大数要么是小于等于该数的最大值,要么是小于等于该数的次大值,\(\color{red}{详细一点,为什么?}\) 设小于等于 \(x\) 的斐波那契数的最大值为 \(f[i]\),组成 \(x\) 的斐波那契数的最大值为小于等于 \(x\) 的次次大值 \(f[i-2]\),则有 \(s[i-2]=f[i]+1\)\(f[i]\leq x\)\(s[i-2]-1\leq x\),即 \(s[i-2]<x\),而由于小于等于 \(x\) 的最大斐波那契数为 \(f[i-2]\),则 \(x\leq s[i-2]\),与 \(s[i-2]<x\) 矛盾,故假设不成立,所以最大数是最大值与次大值两者取一,注意不能同时取,如果同时取的话,由斐波那契数列,\(f[i+1]=f[i]+f[i-1]\),即同时取的时候,最大的那个斐波那契数应该是 \(f[i+1]\),与定义的 \(f[i]\) 矛盾,故只能两者取一,利用这一点进行 \(dp\) 转移即可,另外注意在取较大值 \(f[i]\) 时,可能选取的 \(x-f[i]\) 里面会有等于 \(f[i]\) 的元素,而要求的是不同元素,所以在转移时当出现相同元素时这部分不应该统计进答案,即判断小于等于 \(x-f[i]\) 的最大和次大的斐波那契数是否等于 \(f[i]\)。另外简单证明一下为什么选取最大数 \(f[i]\) 时,\(x-f[i]\) 里面不会出现 \(f[i]\),设 \(x-f[i]\) 里面出现了 \(f[i]\),则 \(f[i]\leq x-f[i]\)\(2f[i]\leq x\)\(f[i]+f[i-1]=f[i+1]\leq 2f[i]\leq x\),则小于等于 \(x\) 的最大斐波那契数应该是 \(f[i+1]\) 才对,与 \(f[i]\) 的定义矛盾,故假设不成立,证毕

\(p\) 为小于等于 \(x\) 的最大的斐波那契数的下标,则

  • 状态表示:

    • \(f[x][0]\) 表示 \(x\) 选取最大的那个斐波那契数的乘积和
    • \(f[x][1]\) 表示 \(x\) 选取次大的那个斐波那契数的乘积和

满足要求的斐波那契数才 \(3\) 个,所以二分寻找小于等于 \(x\) 的最大的斐波那契数的复杂度可以忽略不计,则:

  • 时间复杂度:\(O(n)\)

代码

// Problem: Byfibonacci
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/B
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e7+5,mod=998244353;
int t,n,a[50],cnt=2,f[N][2];
int main()
{
    f[0][0]=f[1][0]=f[1][1]=a[0]=a[1]=1;
    while(a[cnt-1]+a[cnt-2]<=1e7)a[cnt]=a[cnt-1]+a[cnt-2],cnt++;
    cnt--;
    for(int i=2;i<=1e7;i++)
    {
    	int p=lower_bound(a,a+cnt+1,i)-a;
    	if(a[p]!=i)p--;
    	f[i][0]=1ll*(f[i-a[p]][0]+f[i-a[p]][1])*a[p]%mod;
    	int pp=lower_bound(a,a+cnt+1,i-a[p-1])-a;
    	if(a[pp]!=i-a[p-1])pp--;
        if(a[pp-1]!=a[p-1])f[i][1]=1ll*f[i-a[p-1]][1]*a[p-1]%mod;
    	if(a[pp]!=a[p-1])f[i][1]=(f[i][1]+1ll*f[i-a[p-1]][0]*a[p-1]%mod)%mod;
    }
    for(read(t);t;t--)
    {
    	read(n);
    	printf("%d\n",(f[n][0]+f[n][1])%mod);
    }
    return 0;
}

C.Conspicuousness

给定一个由 \(1-9\) 组成的长度范围为(\(3\times 10^5\))的字符串,并给出 \(q(q <= 3\times 10^5)\) 次询问,每次给出个长度 \(k\), 求出该字符串中长度至少为 \(k\) 的子串的最多出现次数

解题思路

后缀自动机

后缀自动机裸题,本题拿后缀自动机中的树求解即可,即通过树求得 \(\mid enspos[i] \mid\),其中 \(i\) 表示状态节点的大小,用其更新长度 \(node[i].len\) 的贡献,虽然 \(node[i].len\) 表示的是状态节点 \(i\) 里面表示的子串的最长长度,但由于最后求解的是至少长度为 \(k\) 的子串的最多出现次数,且长度越小的子串的出现次数越多,所以需逆序更新最大值,故可以直接拿 \(node[i].len\) 更新答案

  • 时间复杂度:\(O(n)\)

代码

// Problem: Conspicuousness
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/C
// Memory Limit: 524288 MB
// Time Limit: 4000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=3e5+5;
int n,q,endpos[N<<1],f[N<<1],cnt=1,lst=1;
vector<int> adj[N<<1];
char s[N];
struct Node
{
	int len,fa;
	int ch[10];
}node[N<<1];

void extend(int c)
{
	int p=lst,np=lst=++cnt;
	node[np].len=node[p].len+1;
	endpos[np]=1;
	for(;p&&!node[p].ch[c];p=node[p].fa)node[p].ch[c]=np;
	if(!p)node[np].fa=1;
	else
	{
		int q=node[p].ch[c];
		if(node[q].len==node[p].len+1)node[np].fa=q;
		else
		{
			int nq=++cnt;
			node[nq]=node[q];
			node[nq].len=node[p].len+1;
			node[np].fa=node[q].fa=nq;
			for(;p&&node[p].ch[c]==q;p=node[p].fa)node[p].ch[c]=nq;
		}
	}
}
void dfs(int x)
{
	for(int y:adj[x])
	{
		dfs(y);
		endpos[x]+=endpos[y];
	}
	f[node[x].len]=max(f[node[x].len],endpos[x]);
}
int main()
{
    scanf("%d%d",&n,&q);
    scanf("%s",s);
    for(int i=0;s[i];i++)extend(s[i]-'0');
    for(int i=1;i<=cnt;i++)adj[node[i].fa].pb(i);
    dfs(1);
    for(int i=n;i>=1;i--)f[i]=max(f[i],f[i+1]);
    while(q--)
    {
    	int x;
    	scanf("%d",&x);
    	printf("%d\n",f[x]);
    }
    return 0;
}

D.Double

打架游戏,给出 \(n\) 个战斗力。战斗力大的能战胜小的,相等的胜率一半,并且只能和相邻的战斗,每次胜利之后战斗力会翻倍,要求输出所有可能胜利到最后的人的序号

解题思路

思维

由于每次翻倍且范围为 \(1,10^9\),所以对于一个数来说,操作能大于等于 \(30\) 次就胜利了,故对于一个数可以左右操作直到左右都不能操作或者操作数大于等于 \(30\) 时停止对该数的操作

  • 时间复杂度:\(O(30n)\)

代码

// Problem: Double
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/D
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=5e5+5;
int n,a[N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	vector<int> res;
	a[0]=a[n+1]=0x3f3f3f3f;
	for(int i=1;i<=n;i++)
	{
		LL s=a[i];
		int l=i,r=i;
		bool f=false;
		while(true)
		{
			while(l-1>=1&&a[l-1]<=s)s<<=1,l--;
			while(r+1<=n&&a[r+1]<=s)s<<=1,r++;
			if(l==1&&r==n||i-l+r-i>=30)
			{
				f=true;
				break;
			}
			if(a[l-1]>s&&a[r+1]>s)break;
		}
		if(f)res.pb(i);
	}
	cout<<res.size()<<'\n';
	for(int i:res)cout<<i<<' ';
    return 0;
}

F.Fake Math Problem

给出一个序列 \(a_0,a_1\dots,a_n\),求 \(\sum_{i=0}^{n} \sum_{j=0}^{a_{i}} P(i, j) \bmod 998241383\),其中 \(P(i, j)=\underbrace{i \cdot(i-1) \cdot(i-2) \cdots(i-j+1)}_{j \text { factors }}\)

\(\begin{aligned} &n\left(1 \leq n \leq 10^{5}\right) \\ &a_{0}, a_{1}, \cdots, a_{n}\left(0 \leq a_{i} \leq i\right) \end{aligned}\)

解题思路

暴力

关键点在于 \(998241383=673*937*1583\),乘法计算最多乘 \(1583\) 次后面模 \(998241383\) 就为 \(0\) 了,然后暴力即可

  • 时间复杂度:\(O(1583n)\)

代码

// Problem: Fake Math Problem
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/F
// Memory Limit: 524288 MB
// Time Limit: 6000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int mod=673*937*1583;
int t,n,x,res;
int main()
{
    for(cin>>t;t;t--)
    {
    	res=0;
    	cin>>n;
    	for(int i=0;i<=n;i++)
    	{
    		cin>>x;
    		int t=1;
    		for(int j=0;j<=x&&t;j++)
    		{
    			res=(res+t)%mod;
    			t=1ll*t*(i-j)%mod;
    		}
    	}
    	cout<<res<<'\n';
    }
    return 0;
}

G.Good Game, GG

\(Alice\)\(Bob\) 玩一场游戏,给定 \(n\) 个整数。
\(Alice\) 有两种操作:
① 选一个奇数,将其分成两个整数
② 拿掉一个 \(1\)
\(Bob\) 只有 $1 $种操作:选择一个偶数并且将这个偶数拆分成两个数

\(Alice\) 为先手,判断谁是winner。

解题思路

博弈论

首先对于 \(1\),只能 \(Alice\) 操作而 \(Bob\) 不能操作,对 \(2\)\(Bob\) 只能将其拆成 \(1,1\),这样反而 \(Alice\) 还比 \(Bob\) 多操作一次,\(Bob\) 肯定不会操作 \(2\),故相当于 \(2\) 没用,而 \(奇数=奇数+偶数\),故对于 \(Alice\) 来说,如果 \(奇数 x\neq 1\) 的话,其必定会构造出一个偶数,这样最好是给 \(Bob\) 构造无用的 \(2\),其操作数为 \(\frac{x+1}{2}\);而对于偶数 \(x\)\(Bob\) 一定会划分为两个偶数,以划分为 \(x-2,2\) 为例,其操作数为 \(\frac{x}{2}-1\),比较两操作数即可

  • 时间复杂度:\(O(n)\)

代码

// Problem: Good Game, GG
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/G
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

int t,n,x;
int main()
{
    for(cin>>t;t;t--)
    {
    	cin>>n;
    	LL cnt1=0,cnt2=0;
    	while(n--)
    	{
    		cin>>x;
    		if(x&1)cnt1+=x+1>>1;
    		else
    			cnt2+=x/2-1;
    	}
    	puts(cnt1>cnt2?"Alice":"Bob");
    }
    return 0;
}

H.History

给定一张 \(n\) 个点,\(m\) 条带权边的图,每到一个点所有边权 \(x\) 都根据 \(next(x)=\frac{1+x}{1-x}\%p\)(其中 \(p\) 为质数)变化,求最短路

\(1\leq n,m\leq 2\times 10^5,5\leq p\leq 10^9\)

解题思路

分层图

考虑 \(next(x)=\frac{1+x}{1-x}\) 式子的变化,设 \(x=tan(t)\),则 \(next(tan(t))=\frac{1+tan(t)}{1-tan(t)}=tan(t+\frac{\pi}{4})\),而 \(tan(t)=tan(t+\pi)\),故权值为周期为 \(4\) 的一个循环,这样可利用分层图求解。这与一般的分层图不同,一般的分层图从建图入手,而这里从设置状态入手,即用 \(d[i][j]\) 表示到节点 \(i\) 且在 \(j\) 层的最短路,当然层数不好设置,但由于只有 \(4\) 个权值,所以需将 \(j\)\(4\) 取模

  • 时间复杂度:\(O(4\times (mlog(n)+log(p))\)

代码

// Problem: History
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/36906/H
// Memory Limit: 524288 MB
// Time Limit: 4000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
// #define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=2e5+5;
int n,m,p;
LL d[N][4];
vector<PII> adj[N];
struct Node
{
	LL dis;
	int x,d;
};
bool operator<(Node a,Node b)
{
	return a.dis>b.dis;
}
int ksm(int a,int b,int p)
{
	int res=1%p;
	while(b)
	{
		if(b&1)res=1ll*res*a%p;
		a=1ll*a*a%p;
		b>>=1;
	}
	return res;
}
int cal(int x,int t)
{
	int res=x;
	for(int i=0;i<t;i++)res=1ll*(1+res)*ksm(((1-res)%p+p)%p,p-2,p)%p;
	return res;
}
void dijkstra()
{
	priority_queue<Node> q;
	memset(d,0x3f,sizeof d);
	q.push({0,1,0});
	d[1][0]=0;
	while(q.size())
	{
		auto t=q.top();
		q.pop();
		int x=t.x,pos=t.d;
		if(d[x][pos]!=t.dis)continue;
		for(auto t:adj[x])
		{
			int y=t.fi,w=t.se;
			int npos=(pos+1)%4;
			int nw=cal(w,pos);
			if(d[y][npos]>d[x][pos]+nw)
			{
				d[y][npos]=d[x][pos]+nw;
				q.push({d[y][npos],y,npos});
			}
		}
	}
}
int main()
{
    read(n),read(m),read(p);
    for(int i=1;i<=m;i++)
    {
    	int x,y,z;
    	read(x),read(y),read(z);
    	adj[x].pb({y,z});
    }
    dijkstra();
    LL res=1e18;
    for(int i=0;i<4;i++)res=min(res,d[n][i]);
    cout<<res;
    return 0;
}
posted @ 2022-07-03 09:59  zyy2001  阅读(204)  评论(0编辑  收藏  举报