CSP2021 题解

Posted cjTQX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CSP2021 题解相关的知识,希望对你有一定的参考价值。

廊桥分配

考虑一个重要结论:对廊桥进行标号,每次将飞机加入廊桥时自动加入编号最小的廊桥,答案一定不会变化。

在此基础上,我们可以发现新增一个廊桥时,只会在新增的廊桥上增加飞机,不会影响其他廊桥上的飞机。因此,我们可以假设有无限个廊桥,那么一架飞机如果停在了第 \\(i\\) 座廊桥上,则只要廊桥数量 \\(\\ge i\\) 它就总能找到位置。因此,我们只需要用一个堆来模拟无限廊桥的过程就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m1,m2,d[N],cnt,pos[N],tp[N],p[N],ct1[N],ct2[N];
struct line{
	int l,r;
}a[N],b[N];

int main(){
	scanf("%d%d%d",&n,&m1,&m2);
	for(int i=1;i<=m1;++i)
		scanf("%d%d",&a[i].l,&a[i].r),d[++cnt]=a[i].l,d[++cnt]=a[i].r;
	for(int i=1;i<=m2;++i)
		scanf("%d%d",&b[i].l,&b[i].r),d[++cnt]=b[i].l,d[++cnt]=b[i].r;
	sort(d+1,d+cnt+1);
	cnt=unique(d+1,d+cnt+1)-d-1;
	for(int i=1;i<=m1;++i){
		a[i].l=lower_bound(d+1,d+cnt+1,a[i].l)-d;
		a[i].r=lower_bound(d+1,d+cnt+1,a[i].r)-d;
		pos[a[i].l]=i;tp[a[i].l]=1;
		pos[a[i].r]=i;tp[a[i].r]=2;
	}
	priority_queue<int,vector<int>,greater<int> > q;
	for(int i=1;i<=m1;++i) q.push(i);
	for(int i=1;i<=cnt;++i){
		if(!tp[i]) continue;
		if(tp[i]==1){
			p[pos[i]]=q.top();ct1[p[pos[i]]]++;
			q.pop();
		}
		else q.push(p[pos[i]]);
	}
	while(!q.empty()) q.pop();
	for(int i=1;i<=m2;++i) q.push(i);
	for(int i=1;i<=m2;++i){
		b[i].l=lower_bound(d+1,d+cnt+1,b[i].l)-d;
		b[i].r=lower_bound(d+1,d+cnt+1,b[i].r)-d;
		pos[b[i].l]=i;tp[b[i].l]=3;
		pos[b[i].r]=i;tp[b[i].r]=4;
	}
	for(int i=1;i<=cnt;++i){
		if(tp[i]<3) continue;
		if(tp[i]==3){
			p[pos[i]]=q.top();ct2[p[pos[i]]]++;
			q.pop();
		}
		else q.push(p[pos[i]]);
	}
	
	int ans=0,now=0;
	for(int i=1;i<=n;++i) now+=ct1[i];ans=now;
	for(int i=n-1,j=1;i>=0;--i,++j){
		now-=ct1[i+1];now+=ct2[j];
		ans=max(ans,now);
	}
	printf("%d\\n",ans);
	return 0;
}

括号序列

题目很明显是一道区间 DP,一眼看过去可能会觉得这就是在一个合法括号序列的每两个括号之间都可以加 \\(\\le k\\) 个 * 号,但手玩样例会发现在 \\((A()B)\\) 的情况下,\\(A,B\\) 不能同时放 * 号。

对此,我的做法是设了三个 \\(dp\\) 数组,\\(f_{l,r},g_{l,r},h_{l,r}\\) 分别表示 \\([l,r]\\) 区间组成 \\(A、(A)、SA\\) 的方案,其中 \\(A\\) 是一个合法序列,\\(S\\) 是由长度不超过 \\(k\\) 的 * 号组成的序列,然而就很容易进行 \\(\\mathcal O(n^3)\\) 的转移了。

#include<bits/stdc++.h>
using namespace std;
const int N=510,mod=1e9+7;
char s[N];
int n,k,f[N][N],g[N][N],h[N][N];
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline bool pd(int x,char c){
	if(s[x]==c||s[x]==\'?\') return true;
	return false;
}
int main(){
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	for(int i=0;i<=n+1;++i) f[i][i-1]=g[i][i-1]=h[i][i-1]=1;
	for(int len=2;len<=n;++len){
		for(int l=1;l+len-1<=n;++l){
			int r=l+len-1;
			//g[l][r]
			if(pd(l,\'(\')&&pd(r,\')\')){
				for(int i=l;i<r&&i<=l+k;++i){
					inc(g[l][r],f[i+1][r-1]);
					if(!pd(i+1,\'*\')) break;
				}
				if(pd(r-1,\'*\')){
					for(int i=r-1;i>l+1&&i>=r-k;--i){
						inc(g[l][r],f[l+1][i-1]);
						if(!pd(i-1,\'*\')) break;
					}
				}
			}
			//f[l][r]
			if(pd(l,\'(\')){
				for(int mid=l+1;mid<=r;++mid){
					if(!pd(mid,\')\')) continue;
					inc(f[l][r],1ll*g[l][mid]*h[mid+1][r]%mod);
				}
			}
			//h[l][r]
			for(int i=l;i<=r&&i<=l+k;++i){
				inc(h[l][r],f[i][r]);
				if(!pd(i,\'*\')) break;
			}
		}
	}
	printf("%d\\n",f[1][n]);
	return 0;
}

回文

考虑进行第一步操作后,就同时确定了最后一个取出的数的位置 \\(p\\),那么 \\(p\\) 左侧的数必须向左取,\\(p\\) 右侧的数必须向右取,将这两部分分别看成两个栈,那么每一步取出的必须是某一个栈的栈顶,并且它的另一个位置必须是某一个栈的栈底,那么只需要贪心的优先删左边,删不了就删右边即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
map<pair<int,int>,int>mp;
int T,n,a[N],L[N],R[N],ct[N];
bool fl=0;
char s[N];
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<\'0\'||ch>\'9\'){if(ch==\'-\') f=-1;ch=getchar();}
	while(ch>=\'0\'&&ch<=\'9\'){x=x*10+(ch^48);ch=getchar();}
	return x*f;
}
inline bool solve(char c,int i,int j,int ql,int qr){
	int now=0;
	s[++now]=c;
	while(i<ql){
		while(j>qr&&ql!=R[a[i]]+1&&qr!=R[a[i]]-1){
			s[++now]=\'R\';
			if(ql==L[a[j]]+1) s[(n<<1)-now+1]=\'L\',ql--;
			else if(qr==L[a[j]]-1) s[(n<<1)-now+1]=\'R\',qr++;
			else return false;
			j--;
		}
		if(i>=ql) break;
		if(ql!=R[a[i]]+1&&qr!=R[a[i]]-1) return false;
		if(ql==R[a[i]]+1) s[++now]=\'L\',ql=R[a[i]],s[(n<<1)-now+1]=\'L\';
		else s[++now]=\'L\',qr=R[a[i]],s[(n<<1)-now+1]=\'R\';
		i++;
	}
	while(j>qr){
		s[++now]=\'R\';
		if(now!=1){
			if(ql==L[a[j]]+1) s[(n<<1)-now+1]=\'L\',ql--;
			else if(qr==L[a[j]]-1) s[(n<<1)-now+1]=\'R\',qr++;
			else return false;
		}
		else ql=qr=L[a[j]];
		j--;
	}
	s[n<<1]=\'L\';
	for(int i=1;i<=(n<<1);++i) putchar(s[i]);
	puts("");
	return true;
}
int main(){
	T=read();
	while(T--){
		n=read();
		for(int i=1;i<=n;++i) L[i]=R[i]=-1;
		for(int i=1;i<=(n<<1);++i){
			a[i]=read();
			if(L[a[i]]==-1) L[a[i]]=i;
			else R[a[i]]=i;
		}
		if(solve(\'L\',2,n<<1,R[a[1]],R[a[1]])) continue;
		else if(solve(\'R\',1,(n<<1)-1,L[a[n<<1]],L[a[n<<1]])) continue;
		else puts("-1");
	}
	return 0;
}

交通规划

首先考虑 \\(k=2\\) 的时候,此时我们固定了两条附加边的颜色,如果它们颜色相同则答案显然是 \\(0\\),否则我们应当将所有点分成两种颜色的连通块,也就相当于在对偶图上连一条最短路径将两个点分开,这是可以通过最短路完成的。

\\(k>2\\) 时,我们仍然希望只连接颜色相同的点之间的边,将每个点分成若干个不同颜色的连通块后,满足所有不同颜色的附加边不在同一个连通块内。转化成对偶图后,这就相当于是在下图中两种颜色的点之间连边使得所有相邻的不同颜色点都被隔开。

于是问题就转化为了在图中蓝色连续段和橙色连续段之间连边,将蓝色与橙色两两匹配,匹配代价就是它们的最短路。可以发现此时如果我们连接交叉的两条边 \\((x_1,y_1),(x_2,y_2)\\),那么一定可以通过交换成 \\((x_1,y_2),(x_2,y_1)\\) 而使答案不会更劣。

因此我们只用连接的边之间一定只会包含或相离,可以对此进行区间 \\(DP\\),最终总复杂度为 \\(\\mathcal O(k^3+knm\\log nm)\\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=510;
int n,m,T,cnt,first[N*N],pos[N<<2],vis[N*N],ve[N];ll dis[N*N];
pair<int,int> a[N];
inline int id(int x,int y){return (x-1)*(m+1)+y;}
struct node{
	int v,w,nxt;
}e[N*N*4];
struct pt{
	int x,p,c;
}p[N];
bool operator <(const pt &a,const pt &b){return a.p<b.p;}
inline void add(int u,int v,int w){
	e[++cnt]=(node){v,w,first[u]};first[u]=cnt;
	e[++cnt]=(node){u,w,first[v]};first[v]=cnt;	
}
inline void dij(int x){
	int all=(n+1)*(m+1);
	memset(dis+1,0x3f,sizeof(ll)*(all));
	memset(vis+1,0,sizeof(int)*(all));
	priority_queue<pair<ll,int> > q;
	dis[x]=0;q.push(make_pair(0,x));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;vis[u]=1;
		for(int i=first[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[u]+e[i].w<dis[v]){
				dis[v]=dis[u]+e[i].w;
				q.push(make_pair(-dis[v],v));
			} 
		}
	}
}
ll f[N][N],d[N][N];
int pre[N],nxt[N];
int main(){
	scanf("%d%d%d",&n,&m,&T);
	for(int i=1;i<n;++i) for(int j=1,w;j<=m;++j) scanf("%d",&w),add(id(i+1,j),id(i+1,j+1),w);
	for(int i=1;i<=n;++i) for(int j=1,w;j<m;++j) scanf("%d",&w),add(id(i,j+1),id(i+1,j+1),w);
	for(int i=1;i<=m;++i) add(id(1,i),id(1,i+1),0),pos[i]=cnt-1;
	for(int i=1;i<=n;++i) add(id(i,m+1),id(i+1,m+1),0),pos[i+m]=cnt-1;
	for(int i=1;i<=m;++i) add(id(n+1,m+2-i),id(n+1,m+1-i),0),pos[i+m+n]=cnt-1;
	for(int i=1;i<=n;++i) add(id(n+2-i,1),id(n+1-i,1),0),pos[i+m+n+m]=cnt-1;
	int all=n+m+n+m;
	while(T--){
		int k;scanf("%d",&k);
		for(int i=0;i<k;++i){
			scanf("%d%d%d",&p[i].x,&p[i].p,&p[i].c);
			e[pos[p[i].p]].w=e[pos[p[i].p]+1].w=p[i].x;
		}
		sort(p,p+k);
		int tot=0;
		for(int i=0;i<k;++i)
			if(p[i].c!=p[(i+1)%k].c){
				int j=p[i].p,&x=ve[tot];
				if(j<=m) x=id(1,j+1);
				else if(j<=m+n) x=id(j-m+1,m+1);
				else if(j<=m+n+m) x=id(n+1,m+n+m+1-j);
				else x=id(m+n+m+n+1-j,1);
				tot++;
			}
		for(int i=0;i<tot;++i){
			dij(ve[i]);
			for(int j=i+1;j<tot;++j){
				d[i][j]=dis[ve[j]]; 
				d[j][i]=d[i][j];
			}
		}
		for(int i=0;i<tot;++i)
			nxt[i]=(i==tot-1)?0:i+1,pre[i]=i?i-1:tot-1;
		for(int len=2;len<=tot;len+=2){
			for(int l=0;l<tot;++l){
				int r=(l+len-1)%tot;
				f[l][r]=1e15;
				for(int j=nxt[l];;j=nxt[nxt[j]]){
					f[l][r]=min(f[l][r],(j==nxt[l]?0:f[nxt[l]][pre[j]])+d[l][j]+(j==r?0:f[nxt[j]][r]));
					if(j==r) break;
				}
			}
		}
		printf("%lld\\n",f[0][tot-1]);
		for(int i=0;i<k;++i)
			e[pos[p[i].p]].w=e[pos[p[i].p]+1].w=0;
	}
	return 0;
}

$CSP$ $2019$ $Day2$ 模拟考试 题解报告

\\(CSP\\) \\(2019\\) \\(Day2\\) 模拟考试 题解报告

得分情况

\\(T1\\) \\(100\\ Pts\\) (做过)

\\(T2\\) \\(8\\ Pts\\)

\\(T3\\) \\(0\\ Pts\\)

总分: \\(108\\ Pts\\)

考试过程

\\(T1\\) 开始做 先把 \\(84\\) 的写完 跑路

看了一下 \\(T2\\) 看到小数据 写了个爆搜 感觉是 \\(dp\\) 但是没写

\\(T3\\) 想写暴力写不出来 看到一个链的特殊情况 推了一个假结论 满二叉树的情况推不出来 跑路

\\(T1\\) 把剩下分的写完 转 \\(T2\\) \\(dp\\) 推假 凄惨爆蛋

题解

\\(T1\\) Emiya 家今天的饭

容斥

答案为 每行选一个数的方案数减去每行选一个 有一行超过总数的一半的方案数

一定只有一列超过总数的一半 直接枚举 设为 \\(p\\)

状态 \\(f_{i, j, k}\\) 表示前 \\(i\\) 行 当前枚举列选择了 \\(j\\) 个数 其他列选择了 \\(k\\) 个数的方案数

\\(g_{i, j}\\) 表示前 \\(i\\) 行选 \\(j\\) 个的方案数

\\(s_i = \\sum_{j = 1}^m a_{i, j}\\)

转移 $$f_{i, j, k} = \\sum_{j = 0}^i \\sum_{k = 0}^i \\left(f_{i - 1, j, k} + f_{i - 1, j - 1, k} \\times a_{i, p} + f_{i - 1, j, k - 1} \\times \\left(s_i - a_{i, p}\\right)\\right)$$

其中 \\(j + k \\leq i\\)

\\[g_{i, j} = \\sum_{j = 0}^i \\left(g_{i - 1, j} + g_{i - 1, j - 1} \\times s_i\\right) \\]

答案为 $$\\sum_{j = 1}^n g_{n, j} - \\sum_{p = 1}^m \\sum_{j = 0}^n \\sum_{k = 0}^{j - 1} f_{n, j, k}$$

\\(p\\) 直接在循环内统计 同样 \\(j + k \\leq n\\)

显然 复杂度 \\(O(mn^3)\\) 可以通过 \\(84\\ Pts\\)

这一部分的代码

/*
  Time: 5.16
  Worker: Blank_space
  Source: P5664 [CSP-S2019] Emiya 家今天的饭
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, a[110][2021], g[110][110], s[110], f[110][110][110], sum, ans;
/*------------------------------------变量定义*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < \'0\' || ch > \'9\') {if(ch == \'-\') f = -1; ch = getchar();}
	while(ch >= \'0\' && ch <= \'9\') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/

/*----------------------------------------函数*/
signed main() {
	n = read(); m = read(); g[0][0] = 1; f[0][0][0] = 1;
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read(), s[i] += a[i][j];
	for(int i = 1; i <= n; i++) for(int j = 0; j <= i; j++) g[i][j] = (g[i][j] + g[i - 1][j] + (j > 0) * s[i] * g[i - 1][j - 1]) % mod;
	for(int j = 1; j <= n; j++) ans = (ans + g[n][j]) % mod;
	for(int p = 1; p <= m; p++, memset(f, 0, sizeof f), f[0][0][0] = 1)
	{
		for(int i = 1; i <= n; i++) for(int j = 0; j <= i; j++) for(int k = 0; k + j <= i; k++)
			f[i][j][k] = (f[i][j][k] + f[i - 1][j][k] + a[i][p] * (j > 0) * f[i - 1][j - 1][k] % mod + (s[i] - a[i][p]) * (k > 0) * f[i - 1][j][k - 1] % mod) % mod;
		for(int j = 0; j <= n; j++) for(int k = 0; k + j <= n && k < j; k++) sum = (sum + f[n][j][k]) % mod;
	}
	printf("%lld", (ans - sum + mod) % mod);
	return 0;
}

考虑优化

对于 \\(f\\) 的转移 与 \\(j\\)\\(k\\) 的大小无关 设 \\(f_{i, j}\\) 表示前 \\(i\\) 行 当前列比其他列多 \\(j\\) 个的方案数

转移 $$f_{i, j} = \\sum_{j = n - i}^{n + i}\\left(f_{i - 1, j} + f_{i - 1, j - 1} \\times a_{i, p} + f_{i - 1, j + 1} \\times \\left(s_i - a_{i, p}\\right)\\right)$$

复杂度 \\(O(mn^2)\\) 可以通过

代码

/*
  Time: 5.28
  Worker: Blank_space
  Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
/*--------------------------------------头文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定义*/
int n, m, a[110][2021], s[110], g[110][2021], f[110][210], ans, sum;
/*------------------------------------变量定义*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < \'0\' || ch > \'9\') {if(ch == \'-\') f = -1; ch = getchar();}
	while(ch >= \'0\' && ch <= \'9\') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
/*----------------------------------------快读*/

/*----------------------------------------函数*/
signed main() {
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);

    n = read(); m = read(); g[0][0] = 1; f[0][n] = 1;//防止越界 
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read(), s[i] = (s[i] + a[i][j]) % mod;
    for(int i = 1; i <= n; i++) for(int j = 0; j <= i; j++) g[i][j] = (g[i][j] + g[i - 1][j] + (j > 0) * g[i - 1][j - 1] * s[i] % mod) % mod;
    for(int j = 1; j <= n; j++) ans = (ans + g[n][j]) % mod;
    for(int p = 1; p <= m; p++, memset(f, 0, sizeof f), f[0][n] = 1) 
    {
    	for(int i = 1; i <= n; i++) for(int j = n - i; j <= n + i; j++)
    		f[i][j] = (f[i][j] + f[i - 1][j] + (j > 0) * f[i - 1][j - 1] * a[i][p] % mod + f[i - 1][j + 1] * (s[i] - a[i][p]) % mod) % mod;
    	for(int j = n + 1; j <= n << 1; j++) sum = (sum + f[n][j]) % mod;
    }
    printf("%lld", (ans - sum + mod) % mod);
    
	fclose(stdin);
	fclose(stdout);
	return 0;
}
/*
2 3
1 0 1
0 1 1

3 3
1 2 3
4 5 0
6 0 0


*/

\\(T2\\) 划分

\\(ps\\) : 赛后 一旁的 \\(zxs\\) 表示这不就是个数的划分的加强版 不随便写一个 \\(dp\\) 就有六十多分 闻言 我只能在一边疯狂膜拜

其实做的时候看了一下第三个样例 我就感觉这题不对劲 高精一如既往的恶心

由完全平方公式: \\(\\left(a + b\\right)^2 \\geq a^2 + b^2\\) 所以多分段一定会更优

状态: \\(f_i\\) 表示以 \\(i\\) 结尾 前面分为若干段(一定是尽量多分)的最小代价

\\(s\\) 数组表示前缀和 转移的同时维护一个 \\(g\\) 数组 记录上一个转移的位置

转移: \\(f_i = \\min\\{f_j + (s_i - s_j)^2 \\}\\)

转移的条件是 \\(s_i - s_j \\geq s_j - s_{g_j}\\)\\(f_j + (s_i - s_j)^2 < f_i\\)

通过枚举 \\(j\\) 每次转移是 \\(O(n)\\)

总复杂度 \\(O(n^2)\\)

期望得分 \\(64\\ Pts\\)

实际得分 \\(64\\ Pts\\)

考虑优化

观察转移条件: \\(s_i - s_j \\geq s_j - s_{g_j}\\)

移项: \\(s_i \\geq 2s_j - s_{g_j}\\)

那么如果有 \\(j\\)\\(k\\) 两个决策点 都可以转移到 \\(i\\) 若是 \\(k \\leq j\\) 因为分段越多答案越优 所以 \\(k\\) 这个点就没有用了 这样就可以通过单调队列进行优化

当一个元素的下一个元素仍然满足 \\(\\geq s_i\\) 这一条件时 那么这个元素就可以出队了

当有一个新的决策点时 维护队列单调递增

这样可以将转移优化到 \\(O(1)\\)

总时间复杂度 \\(O(n)\\)

期望得分 \\(100\\ Pts\\)

实际得分 \\(88\\ Pts\\)

后三个数据点的数据过大 炸掉了 \\(long\\ long\\)

代码

/*
  Time: 5.28
  Worker: Blank_space
  Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define int long long
/*--------------------------------------头文件*/
const int M = 1e8;
const int B = 1e5 + 7;
const int D = 1e7 + 7;
const int mod = 1 << 30;
const int INF = 1e18;
/*------------------------------------常量定义*/
int m, n, type, a[D << 2], s[D << 2], ans = INF, f[D << 2], g[D << 2], q[D << 2], l, r;
int _p[B], _l[B], _r[B], b[D << 2];
/*------------------------------------变量定义*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < \'0\' || ch > \'9\') {if(ch == \'-\') f = -1; ch = getchar();}
	while(ch >= \'0\' && ch <= \'9\') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
inline void print(int x) {if(x > 9) print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------快读*/

/*----------------------------------------函数*/
signed main() {
//	freopen("partition.in","r",stdin);
//	freopen("partition.out","w",stdout);

    n = read(); type = read();
    if(type == 0) for(int i = 1; i <= n; i++) s[i] = s[i - 1] + read();
    else {
    	int x = read(), y = read(), z = read(); b[1] = read(), b[2] = read(), m = read();
    	for(int i = 1; i <= m; i++) _p[i] = read(), _l[i] = read(), _r[i] = read();
    	for(int i = 3; i <= n; i++) b[i] = (b[i - 1] * x + b[i - 2] * y + z) % mod;
    	for(int j = 1; j <= m; j++) for(int i = _p[j - 1] + 1; i <= _p[j]; i++) a[i] = (b[i] % (_r[j] - _l[j] + 1)) + _l[j], s[i] = s[i - 1] + a[i];
    }
    memset(f, 63, sizeof f); f[0] = 0; g[0] = 0; q[l = r = 0] = 0;
    for(int i = 1; i <= n; i++)
    {
    	while(l < r && s[i] - s[q[l + 1]] >= s[q[l + 1]] - s[g[q[l + 1]]]) l++;
    	f[i] = f[q[l]] + (s[i] - s[q[l]]) * (s[i] - s[q[l]]); g[i] = q[l];
    	while(l < r && 2 * s[q[r]] - s[g[q[r]]] >= 2 * s[i] - s[g[i]]) r--;
		q[++r] = i;
    }
    print(f[n]);
    
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

可以直接开 \\(\\_\\_int128\\) 强过 时间复杂度是不变的

也可以选择写高精

\\(\\_\\_128\\) \\(AC\\) 代码

/*
  Time: 5.30
  Worker: Blank_space
  Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<iostream>
#define int long long
#define ll __int128
/*--------------------------------------头文件*/
const int M = 1e8;
const int B = 1e5 + 7;
const int D = 1e7 + 7;
const int mod = 1 << 30;
const int INF = 1e18;
/*------------------------------------常量定义*/
int m, n, _p, type, s[D << 2], g[D << 2], q[D << 2], l, r, tmp;
ll ans = 0;
/*------------------------------------变量定义*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < \'0\' || ch > \'9\') {if(ch == \'-\') f = -1; ch = getchar();}
	while(ch >= \'0\' && ch <= \'9\') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
inline void print(ll x) {if(x > 9) print(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------快读*/

/*----------------------------------------函数*/
signed main() {
//	freopen("partition.in","r",stdin);
//	freopen("partition.out","w",stdout);

    n = read(); type = read();
    if(type == 0) for(int i = 1; i <= n; i++) s[i] = s[i - 1] + read();
    else {
    	int x = read(), y = read(), z = read(), b1 = read(), b2 = read(), m = read();
    	for(int j = 1; j <= m; j++)
    	{
    		int p = read(), _l = read(), _r = read();
    		for(int i = _p + 1; i <= p; i++)
    			if(i == 1) s[i] = b1 % (_r - _l + 1) + _l;
    			else s[i] = b2 % (_r - _l + 1) + _l, tmp = (b2 * x + b1 * y + z) % mod, b1 = b2, b2 = tmp;
    		_p = p;
    	}
    	for(int i = 1; i <= n; i++) s[i] += s[i - 1];
    }
    q[l = r = 0] = 0;
    for(int i = 1; i <= n; i++)
    {
    	while(l < r && s[i] - s[q[l + 1]] >= s[q[l + 1]] - s[g[q[l + 1]]]) l++;
    	g[i] = q[l];
    	while(l < r && 2 * s[q[r]] - s[g[q[r]]] >= 2 * s[i] - s[g[i]]) r--;
		q[++r] = i;
    }
    for(int i = n; i; i = g[i]) ans += (ll)(s[i] - s[g[i]]) * (s[i] - s[g[i]]);
    print(ans);
    
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

高精度比较恶心 这个题卡时间还卡空间

自己写完后调了接近半个小时 改了很多地方 但是由于常数过大 不开 \\(O2\\) 的情况下依然只有 \\(88\\ Pts\\) 吸氧可以过

不知道题解里面有没有用高精过的代码 大多数都是直接用 \\(\\_\\_128\\) 水过去了

高精代码

/*
  Time: 5.30
  Worker: Blank_space
  Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#define ll long long
#define Max(x, y) ((x) > (y) ? (x) : (y))
/*--------------------------------------头文件*/
const int M = 1e8;
const int B = 1e5 + 7;
const int D = 1e7 + 7;
const int mod = (1 << 30) - 1;
const int INF = 1e18;
/*------------------------------------常量定义*/
int m, n, _p, type, g[D << 2], q[D << 2], l, r, tmp;
ll s[D << 2];
/*------------------------------------变量定义*/
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < \'0\' || ch > \'9\') {if(ch == \'-\') f = -1; ch = getchar();}
	while(ch >= \'0\' && ch <= \'9\') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
inline void W(ll x) {if(x > 9) W(x / 10); putchar(x % 10 ^ 48);}
/*----------------------------------------快读*/
struct node {
	ll a[6];
	int len;
	node() {for(int i = 0; i <= 5; i++) a[i] = 0; len = 1;}
	void print() {
		printf("%lld", a[len]);
		for(int i = len - 1; i; i--) printf("%08lld", a[i]);
		puts("");
	}
} ans;
node operator + (node x, ll y) {
	for(int i = 1; i <= 5 && y; i++)
	{
		x.a[i] += y % M; y /= M;
		y += x.a[i] / M; x.a[i] %= M;
	}
	while(x.a[x.len + 1]) x.len++;
	return x;
}
node operator * (node x, node y) {
	node z; z.len = x.len + y.len;
	for(int i = 1; i <= x.len; i++) for(int j = 1; j <= y.len; j++)
		z.a[i + j - 1] += x.a[i] * y.a[j];
	for(int i = 1; i <= z.len; i++) z.a[i + 1] += z.a[i] / M, z.a[i] %= M;
	while(!z.a[z.len] && z.len > 1) z.len--;
	return z;
}
node operator & (node x, node y) {
	node z; z.len = Max(x.len, y.len); ll t = 0;
	for(int i = 1; i <= z.len; i++)
	{
		z.a[i] = x.a[i] + y.a[i] + t;
		t = z.a[i] / M; z.a[i] %= M;
	}
	if(t) z.a[++z.len] = t;
	return z;
}
/*----------------------------------------函数*/
signed main() {
//	freopen("partition.in","r",stdin);
//	freopen("partition.out","w",stdout);

    n = read(); type = read();
    if(type == 0) for(int i = 1; i <= n; i++) s[i] = s[i - 1] + read();
    else {
    	int x = read(), y = read(), z = read(), b1 = read(), b2 = read(), m = read();
    	for(int j = 1; j <= m; j++)
    	{
    		int p = read(), _l = read(), _r = read();
    		for(int i = _p + 1; i <= p; i++)
    			if(i == 1) s[i] = b1 % (_r - _l + 1) + _l;
    			else s[i] = b2 % (_r - _l + 1) + _l, tmp = (b2 * x + b1 * y + z) & mod, b1 = b2, b2 = tmp;
    		_p = p;
    	}
    	for(int i = 1; i <= n; i++) s[i] += s[i - 1];
    }
    q[l = r = 0] = 0;
    for(int i = 1; i <= n; i++)
    {
    	while(l < r && s[i] - s[q[l + 1]] >= s[q[l + 1]] - s[g[q[l + 1]]]) l++;
    	g[i] = q[l];
    	while(l < r && 2 * s[q[r]] - s[g[q[r]]] >= 2 * s[i] - s[g[i]]) r--;
		q[++r] = i;
    }
    for(int i = n; i; i = g[i])
	{
		node d;
		d = d + (s[i] - s[g[i]]);
		ans = d * d & ans;
	}
    ans.print();
    
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

\\(T3\\) 树的重心

以上是关于CSP2021 题解的主要内容,如果未能解决你的问题,请参考以下文章

CCF CSP-2021-04 赛题练习

CSP-S2019题解

CSP-J2022试题题解

CSP-J2022山东补赛题解

[题解]CSP2020-S T4 Snakes

[CSP-S模拟测试60]题解