CSP-S 2019 题解(部分)& 游记(伪)

CSP-S 2019 题解(部分)& 游记(伪)

文章同步发表在 cnblogsHexo

day 0 - 8:00 a.m. \text{day 0 - 8:00 a.m.} day 0 - 8:00 a.m.

GM: 欸童鞋们我们今天不学新知识点哦!

We: ヾ(✿゚▽゚)ノ好吔!

GM: ⋯ \cdots 我们要考 CSP-S 2019 \text{CSP-S 2019} CSP-S 2019

We: 鄵儞亇cào nĭ mā

day 1 - 8:10 a.m. \text{day 1 - 8:10 a.m.} day 1 - 8:10 a.m.

一眼望去,啊,这就是传说中的 S \text S S 组难度吗?

T1 格雷码

Problem Link 双倍经验 三倍经验

心路历程

看题比做题久系列

好险,还好敲完了闲着没事自己乱造数据玩,玩出来一组hack

估分 100 pts \text{100 pts} 100 pts 吧。

Solution

题目大意:

给定 n n n k k k ,求按指定规则生成的 n n n 0 ∼ 2 n − 1 0\sim 2^n-1 02n1 的二进制码中的第 k k k 个元素。规则如下:

对于长度为 2 n 2^n 2n n n n 位二进制码序列, 0 ∼ 2 n − 1 − 1 0\sim 2^{n-1}-1 02n11 个元素为长度为 2 n − 1 2^{n-1} 2n1 的序列做高位加上一个 0 0 0 的结果;

后面的元素为长度为 2 n − 1 2^{n-1} 2n1 的二进制序列倒置后最高位加上一个 1 1 1 的结果。

1 1 1 位二进制码序列为 0 , 1 0,1 0,1


数据范围: 1 ⩽ n ⩽ 64 , 0 ⩽ k < 2 n 1\leqslant n\leqslant 64,0\leqslant k<2^n 1n64,0k<2n

不可能模拟啦~

众所周知,数据范围在 long long 规模的,基本上就是 log ⁡ \log log 级的做法了。

这里提供一个码量少的做法。(不要跟我扯那些大佬们的 Θ ( 1 ) \Theta(1) Θ(1) 做法)

对于 n n n 位二进制序列,我们简称 0 ∼ 2 n − 1 − 1 0\sim 2^{n-1}-1 02n11 这一段为前半段,剩余部分为后半段。

如果要求的 k k k 在前半段,那说明不是由 n − 1 n-1 n1 位序列翻转最高位置 1 1 1 得到的,那么最高位就会被置 0 0 0,也就是说第 n n n 位为 0 0 0

否则,最高位为 1 1 1 k k k 会变成 2 n − 1 − [ k − ( 2 n − 1 − 1 ) ] = 2 n − 1 − k 2^{n-1}-[k-(2^{n-1}-1)]=2^n-1-k 2n1[k(2n11)]=2n1k

然后就没了。

代码
#include
#define int long long
int n,k;
signed main(){
	scanf("%lld%lld",&n,&k);
	while(n){
		if(k>=(1<<n-1)){
			putchar('1');
			k=(1<<n)-k-1;
		}
		else putchar('0');
		n--;
	}
	return 0;
}

你 以 为 这 题 就 这?

有两个问题。

  1. 题目中的 k < 2 n k<2^n k<2n,而 n n n 最多可以取到 64 64 64

    long long 的范围是 − 2 63 ∼ 2 63 − 1 -2^{63}\sim 2^{63}-1 2632631,而 unsigned long long 才能达到 0 ∼ 2 64 − 1 0\sim 2^{64}-1 02641

    所以 k k k 要用 unsigned long long 存。

    这样想,代码中的 1< 范围 int 类型的结果,会直接溢出然后 UB \text{UB} UB

    所以保险和节省码量起见,使用 const unsigned long long x 存储 (unsigned long long)1 的值,然后将 1< 替换为 x<

  2. 注意到这一行语句:

    k=(x<<n)-k-1;
    

    数据出的好,可以卡住 x<。(事实上第 20 20 20 个点确实卡了)

    x<<64ull 刚好溢出。直接自然溢出啥事没有

    所以只好把 x<<64 拆成 (x<<63)+(x<<63)

    但是这并没有实质性地解决问题,虽然两个加数都没有爆,但它们之间的和却挂了。

    我们开心地发现后面做了一个减法,于是我们完全可以先 − - + + +

真代码
#include
#define int unsigned long long
const int x=1;
int n,k;
signed main(){
	scanf("%llu%llu",&n,&k);
	while(n){
		if(k>=(x<<n-1)){
			putchar('1');
			k=(x<<n-1)+((x<<n-1)-1)-k;
		}
		else putchar('0');
		n--;
	}
	return 0;
}

关于 hack:

问题一可以用 64 99999999964 10000000000 hack,你会发现它们的值差的不是一点两点。。。

(题目中提到格雷码的一个性质:相邻两个数码恰好一位的值不同)

问题二,可以用 64 18446744073709551615 hack,标答是 1 1 1 后面全是 0 0 0


T2 括号树

Problem Link 双倍经验 三倍经验

心路历程

我鄵cào,T2难度骤增。

先打了个链的部分分,推广到树上就是正解了。

但是我的树形DP(or 树上DP?反正都差不多)传参传的是个长度为 n n nstack,应该会T。

(然后下来我就去跟 @Nefelibata 吹自己常数5e5)

链应该是 Θ ( n ) \Theta(n) Θ(n) 的,没问题,树有点险。

估分 55+rp pts \text{55+rp pts} 55+rp pts

Solution

先把一条链的情况打出来再说。

因为已确定根节点为 1 1 1 f i = i − 1 f_i=i-1 fi=i1,所以 s i = S 1 ∼ S i s_i=S_1\sim S_i si=S1Si,其中 s s s 的含义题目中有给出, S S S 为给定括号串。

k i k_i ki 的含义就可简化为 S 1 ∼ S i S_1\sim S_i S1Si 中的合法子串个数。

相信各位都做过括号匹配之类的题目,这里我们也用一个栈处理。

每遇到一个 ( \texttt ( (push 进去,每遇到一个 ) \texttt ) ) 就判断栈内是否有 ( \texttt ( ( ,有就计算然后 pop

因为要统计从 S 1 ∼ S i S_1\sim S_i S1Si 所有合法括号串,所以定义一个 c n t cnt cnt 表示从 S 1 ∼ S i S_1\sim S_i S1Si 的合法括号串个数,若当前字符可以完成一个匹配,则 cnt+=以第i个元素结尾的合法子串个数

那么,这个“合法子串个数”应该怎样计算呢?

定义 l s t lst lst,表示以当前元素的上一个元素结尾的合法子串个数。

若当前元素是 ( \texttt ( ( ,把 l s t lst lst 丢进栈里,记录如果当前 ( \texttt ( ( 能被匹配,新增的合法括号串个数;

否则当前元素就和之前的合法子串挨不到一起了, l s t lst lst 清零。(突然飚方言)

如果当前元素可以完成一个匹配,则栈顶记录的数就是和当前匹配上的括号紧挨着的之前的合法子串个数,由于当前又新增了一个匹配,记录最新的 l s t lst lst 为栈顶元素 + 1 +1 +1,同时这也是当前的 k i k_i ki。此时 cnt+=lst

可得出以下代码:

//55pts 链 
namespace Task1{
	int lst,cnt;
	inline void solve(void){
		for(int i=1;i<=n;++i){
			if(s[i]=='('){
				stk.push(lst);
				lst=0;
			}
			else if(stk.size()){
				lst=stk.top()+1;
				cnt+=lst;
				stk.pop();
			}
			else lst=0;
			ans^=cnt*i;
		}
		printf("%lld",ans);
		return;
	}
}

然后直接把这些搬到树上即可。

其中, c n t cnt cnt 不能混合起来记了,必须每个点继承一下自己父亲结点的 c n t cnt cnt,然后自个儿记自个儿的。

l s t lst lst 同理。

但是,由于作者清奇的思考方式与码风,她选择了把 c n t cnt cnt 改成数组, l s t lst lst 改成 dfs 时传参。。。

总之,因为dfs在匹配过程中可能出现新增的 ( \texttt ( ( 还没匹配完,留在栈里,或是后面一大堆 ) \texttt ) ),把之前的给匹配掉了,所以必须即时传参。

然后最后统一计算 cnt 值即可。

Code

差点被常数从 Θ ( n ) \Theta(n) Θ(n) 卡成 Θ ( n 2 ) \Theta(n^2) Θ(n2)

#include
#include
#define int long long 
using std::stack;
const int maxn=5e5+5;
bool SF=1;	//Subtask Flag
int f[maxn];
char s[maxn];
int n,tot,ans;
stack<int>stk;
int h[maxn],to[maxn],nxt[maxn];
inline void add(int x,int y){
	to[++tot]=y;
	nxt[tot]=h[x];
	h[x]=tot;
	return;
}
inline void read(int&x){
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=x*10+(ch^48);
		ch=getchar();
	}
	return;
}
int cnt[maxn];
void dfs(int x,int lst,stack<int>st){
	cnt[x]=cnt[f[x]];
	if(s[x]=='('){
		st.push(lst);
		lst=0;
	}
	else if(st.size()){
		lst=st.top()+1;
		cnt[x]+=lst;
		st.pop();
	}
	else lst=0;
	for(int i=h[x];i;i=nxt[i])
		dfs(to[i],lst,st);
	return;
}
inline void solve(void){
	dfs(1,0,stk);
	for(int i=1;i<=n;++i)
		ans^=i*cnt[i];
	printf("%lld",ans);
	return;
}
signed main(){
	freopen("brackets.in","r",stdin);
	freopen("brackets.out","w",stdout);
	read(n);
	scanf("%s",s+1);
	for(int i=2;i<=n;++i){
		read(f[i]);
		add(f[i],i);
	}
	solve();
	return 0;
}
//作者亲自实验:过是一定能过的,出题人不至于来卡我常;但常数确实大得像吃了屎一样。

T3 树上的数

Problem Link 双倍经验 三倍经验

心路历程

草草草还剩不到半个小时了快点暴力冲啊!!!

暴力打出来了,开森。

估分 10 pts \text{10 pts} 10 pts

Solution

我的暴力和题解说的全排暴力不太一样QwQ

正解先咕着,因为题解基于另一种暴力方法改良出正解导致我看不懂


day 1 \text{day 1} day 1 考完即时评测可还行(((

万恶的NOI赛制。

这个时候出成绩了。

CSP-S 2019 题解(部分)& 游记(伪)_第1张图片

草你奶奶我T1WA了?(刚刚是谁说T1有手就行的?)

估分: 100+(55+rp)+10 ≈ 165 pts \text{100+(55+rp)+10}\approx \text{165 pts} 100+(55+rp)+10165 pts

实际: 95 + 100 + 10 = 205 pts {\color{SkyBlue}95}+{\color{Orchid}100}+10=\text{205 pts} 95+100+10=205 pts


day 2 - 2:10 p.m. \text{day 2 - 2:10 p.m.} day 2 - 2:10 p.m.

精准预测:上午考那么好,一定把脑子和 r p rp rp 都耗光了,下午一定被 @Nefelibata 吊打。

T1 Emiya 家今天的饭

Problem Link 双倍经验 三倍经验

心路历程

花了接近 2.5h \text{2.5h} 2.5h 想正解,导致正解没想出来,其他题的暴力分也没时间骗。

时间安排及其不合理,导致与1=失之交臂。。。(莫名押韵)

猜出来了大概是个 Θ ( n 2 × m ) \Theta(n^2\times m) Θ(n2×m) 的DP。

Soution

部分参考了这位金钩爷的博客

题意简化:

给定一个 n × m n\times m n×m 的矩阵,从中选取任意多个数(假设为 k k k),满足:

  1. 每行最多一个数;
  2. 每列最多 ⌊ k 2 ⌋ \large\lfloor\frac{k}{2}\rfloor 2k 个数。

∑ k = 2 n \sum\limits_{k=2}^n k=2n 这些数的乘积(后文称其为总方案数),结果对 998244353 998244353 998244353 取模。

首先,我们知道,条件 1 1 1 很好满足;关键是条件 2 2 2

众所周知,这种求方案数之类的大概率是 DP(而且这道题看起来也挺像的)

首先有一个很简单,但是很重要的性质:不满足条件 2 2 2 的最多只有一列,因为总共就 k k k 个数,不可能分割成两个个数都超过 ⌊ k 2 ⌋ \large\lfloor\frac k2\rfloor 2k 的组。

但这个性质是关于不满足条件 2 2 2 的,我们要求的却是同时满足条件 1 1 1 和条件 2 2 2 的,这时候怎么办呢?

很简单,我们算出满足条件 1 1 1 的总方案数,减去满足条件 1 1 1 且不满足条件 2 2 2 的方案数不就行了?

  1. DP求解满足条件 1 1 1 的总方案数。

    d p i , j dp_{i,j} dpi,j 表示选到第 i i i 行,共选了 j j j 个数,满足条件 1 1 1 的总方案数。
    d p i , j = d p i − 1 , j + ∑ j = 1 m d p i − 1 , j − 1 × a i , j dp_{i,j}=dp_{i-1,j}+\sum\limits_{j=1}^mdp_{i-1,j-1}\times a_{i,j} dpi,j=dpi1,j+j=1mdpi1,j1×ai,j
    小解释: d p i − 1 , j dp_{i-1,j} dpi1,j 表示第 i i i 行不选的情况, ∑ j = 1 m d p i − 1 , j − 1 × a i , j \sum\limits_{j=1}^mdp_{i-1,j-1}\times a_{i,j} j=1mdpi1,j1×ai,j 表示第 i i i 行选 1 ∼ m 1\sim m 1m 某一列上的数的情况。

    s i = ∑ j = 1 m a i , j s_i=\sum\limits_{j=1}^ma_{i,j} si=j=1mai,j,则可以优化为:
    d p i , j = d p i − 1 , j + d p i − 1 , j − 1 × s i dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1}\times s_i dpi,j=dpi1,j+dpi1,j1×si
    最终求得满足条件 1 1 1 的总方案数为 ∑ i = 1 m d p n , i \sum\limits_{i=1}^mdp_{n,i} i=1mdpn,i

    注意,虽说这里求和是从 1 1 1 开始求的,但 j j j 还是得从 0 0 0 开始的异世界生活,所以在 j − 1 j-1 j1 那里要特判一下。

    时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2)

  2. DP求解满足条件 1 1 1 且不满足条件 2 2 2 的总方案数。

    我们设唯一不满足条件的列为 r r r

    枚举每一个 r r r,对每一个不同的 r r r 进行一次DP。

    f i , j , k f_{i,j,k} fi,j,k 表示选到第 i i i 行, r r r 上有 j j j 个数,其他列一共选了 k k k 个数的方案数。
    f i , j , k = f i − 1 , j , k + f i − 1 , j − 1 , k × a i , r + f i − 1 , j , k − 1 × ( s i − a i , r ) f_{i,j,k}=f_{i-1,j,k}+f_{i-1,j-1,k}\times a_{i,r}+f_{i-1,j,k-1}\times (s_i-a_{i,r}) fi,j,k=fi1,j,k+fi1,j1,k×ai,r+fi1,j,k1×(siai,r)
    小解释: f i − 1 , j , k f_{i-1,j,k} fi1,j,k 表示第 i i i 行不选数,直接继承上一行的情况数; f i − 1 , j − 1 , k × a i , r f_{i-1,j-1,k}\times a_{i,r} fi1,j1,k×ai,r 表示选 a i , r a_{i,r} ai,r 的情况; f i − 1 , j , k − 1 × ( s i − a i , r ) f_{i-1,j,k-1}\times (s_i-a_{i,r}) fi1,j,k1×(siai,r) 表示选第 i i i 行其他列的情况总数。

    最终不合法方案总数为 ∑ j > k f n , j , k \large\sum\limits_{j>k} f_{n,j,k} j>kfn,j,k

    为什么是 j > k j>k j>k

    j > j + k 2 j>\frac{j+k}2 j>2j+k 变形得。(无视下取整,反正有没有都差不多)

    时间复杂度 Θ ( n 3 ) \Theta(n^3) Θ(n3),在加上枚举 r r r 的一个 m m m Θ ( n 3 × m ) \Theta(n^3\times m) Θ(n3×m),直接炸掉。

    考虑优化。

    上面求不合法方案总数的限制条件是 j > k j>k j>k,意味着我们并不在意 j j j k k k 的数值具体是多少,只关心它们之间的大小关系。

    所以可以把 f f f 变为二维, f i , j f_{i,j} fi,j 表示已经选到第 i i i 行,第 r r r 列上的数比其他列上的数多 j j j 的总情况数。

    则有:
    f i , j = f i − 1 , j + f i − 1 , j − 1 × a i , r + f i − 1 , j + 1 × ( s i − a i , r ) f_{i,j}=f_{i-1,j}+f_{i-1,j-1}\times a_{i,r}+f_{i-1,j+1}\times(s_i-a_{i,r}) fi,j=fi1,j+fi1,j1×ai,r+fi1,j+1×(siai,r)
    最终答案为 ∑ f n , i \sum f_{n,i} fn,i

    枚举到 i i i 时, j j j 最少可能是 0 − i 0-i 0i(全部选其他列上的),以及最多可能是 i − 0 i-0 i0(全部选 r r r 上的)。为了避免下标出现负数,我们把第二维的下标全部 + n +n +n 处理。(所以开数组时第二维也要多开一个 n n n 啦,不要像作者一样净犯这些SB错误还Debug了一个小时就离谱

    单步时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2),总时间复杂度 Θ ( n 2 × m ) \Theta(n^2\times m) Θ(n2×m)

    可能有些同学会疑惑: a , b ( a > b ) a,b(a>b) a,b(a>b) 之间的差值与 a + 1 , b + 1 a+1,b+1 a+1,b+1 的差值是一样的,像这样算总感觉怪怪的。。。

    可是,我们想想,就算我们把原来的 f i , a , b f_{i,a,b} fi,a,b f i , a + 1 , b + 1 f_{i,a+1,b+1} fi,a+1,b+1 加在一起放在现在的 f i , a − b f_{i,a-b} fi,ab 里面,未经优化的方式最终统计总方案数还是会把 f i , a , b f_{i,a,b} fi,a,b f i , a + 1 , b + 1 f_{i,a+1,b+1} fi,a+1,b+1 加在一起,现在只是相当于提前加了而已,而且仔细分析式子,会发现不会重复计算。虽然看起来确实很怪异,但这种方法确实是正确的。

Code
#include
#include
#define int long long
const int mod=998244353;
int s[105];
int n,m,S,ans;
int a[105][2005];
int f[105][205],dp[105][105];
signed main(){
	#ifdef ONLINE_JUDGE
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
	#endif
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			scanf("%lld",&a[i][j]);
			s[i]=(s[i]+a[i][j])%mod;
		}
	}
	dp[0][0]=1;
	//Step 1: DP求解满足条件1的总方案数
	for(int i=1;i<=n;++i){
		for(int j=0;j<=i;++j){
			dp[i][j]=dp[i-1][j];
			if(j)dp[i][j]=(dp[i][j]+dp[i-1][j-1]*s[i])%mod;
			if(i==n&&j){S+=dp[i][j];S%=mod;}
		}
	}
	//Step 2: DP求解满足条件1且不满足条件2的总方案数
	for(int r=1;r<=m;++r){
		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-1][j]+f[i-1][j-1]*a[i][r]%mod
				+f[i-1][j+1]*(s[i]-a[i][r])%mod;
				f[i][j]%=mod;
			}
		}
		for(int i=1;i<=n;++i)
			ans=(ans+f[n][n+i])%mod;
	}
    //众所周知减法取模保险起见要先加模数
	printf("%lld",(S-ans+mod)%mod);
	return 0;
}

T2 划分

Problem Link 双倍经验 三倍经验

心路历程

距离考试结束还有不到半个小时。

心灰意冷地打开T2。

草!我做过这道题的简化版!推的式子还在U盘里!

此生无憾草你妈要不是旁边的 @Nefelibata 一直用正义猥琐的目光盯着我的电脑屏幕上的题解劳资直接干翻全场!!!

然后在 @Nefelibata 正义的目光下最终没能看成。

Solution

DP。

s i s_i si 表示 ∑ j = 1 i a j \sum\limits_{j=1}^ia_j j=1iaj d i d_i di 为前 i i i 个数据的最小花费, h i h_i hi 表示第 i i i 个数据分配完后, d i d_i di 最小时, i i i 所在组的花费。

此时,以 j + 1 j+1 j+1 为当前组起点, d i = min ⁡ { d j + ( s i − s j ) 2 } d_i=\min\{d_j+(s_i-s_j)^2\} di=min{dj+(sisj)2}

有引理:

i i i 的最优决策,即令 d i d_i di 值最小的 j j j,就是满足 h j ⩽ s i − s j h_j\leqslant s_i-s_j hjsisj max ⁡ { j } \max\{j\} max{j}

感性证明一下。

  1. 证明满足条件的 max ⁡ { j } \max\{j\} max{j} 会使 d i d_i di 最小。

    如图:

    CSP-S 2019 题解(部分)& 游记(伪)_第2张图片

    图中的 j j j 表示满足条件的 max ⁡ { j } \max\{j\} max{j}。则:

    • j j j 为上一分组终点, j + 1 j+1 j+1 为当前分组起点的代价为:

      ( s u m L + x ) 2 + s u m R 2 = s u m L 2 + 2 × x × s u m L + x 2 + s u m R 2 (sumL+x)^2+sumR^2=sumL^2+2\times x\times sumL+x^2+sumR^2 (sumL+x)2+sumR2=sumL2+2×x×sumL+x2+sumR2

    • j − 1 j-1 j1 为上一分组终点, j j j 为当前分组起点的代价为

      s u m L 2 + ( x + s u m R ) 2 = s u m L 2 + x 2 + 2 × x × s u m R + s u m R 2 sumL^2+(x+sumR)^2=sumL^2+x^2+2\times x\times sumR+sumR^2 sumL2+(x+sumR)2=sumL2+x2+2×x×sumR+sumR2

    即比较 2 × x × s u m L 2\times x\times sumL 2×x×sumL 2 × x × s u m R 2\times x\times sumR 2×x×sumR 之间的大小。

    回归条件,由于 j j j 合法,即 h j ⩽ s i − s j h_j\leqslant s_i-s_j hjsisj,换到本图中来说就是 s u m L ⩽ s u m R − x sumL\leqslant sumR-x sumLsumRx

    变形得 s u m L + x ⩽ s u m R sumL+x\leqslant sumR sumL+xsumR。题目保证了 x ⩾ 1 x\geqslant1 x1,得 s u m L ⩽ s u m R sumL\leqslant sumR sumLsumR

    同样因为 x x x 为正数,得 2 × x × s u m L ⩽ 2 × x × s u m R 2\times x\times sumL\leqslant 2\times x\times sumR 2×x×sumL2×x×sumR,即在 j j j 处划分更优。

  2. 证明选择在 max ⁡ { j } \max\{j\} max{j} 处划分不会让后面的答案更劣

    也许我们可以保证在 j j j 处分组可以让 d i d_i di 最小,但这样会不会影响 i i i 之后的结果呢?

    答案是不会。

    j j j 大,意味着 j j j i i i 的差距更小,也就是 h i h_i hi 更小,那后面满足条件 h i ⩽ s i ′ − s i h_i\leqslant s_{i'}-s_i hisisi 的难度就会减小(因为 s i , s i ′ s_i,s_{i'} si,si 的值是固定的),就可以划分更多组,答案最优。

    相信组数越多答案更优的原因大家都知道,但我还是得扯一下:

    a a a b b b 分成两组的花费为 a 2 + b 2 a^2+b^2 a2+b2,而它们合并为一组的花费为 ( a + b ) 2 = a 2 + 2 × a × b + b 2 (a+b)^2=a^2+2\times a\times b+b^2 (a+b)2=a2+2×a×b+b2

    ∵ \because 题目保证 a , b ∈ Z + a,b\in \Z^+ a,bZ+

    ∴ \therefore 前者更优

那么此时我们DP d d d 数组,暴力寻找 max ⁡ { j } \max\{j\} max{j},枚举 1 ∼ n 1\sim n 1n 之间的每一个 i i i,转移 Θ ( n ) \Theta(n) Θ(n),总时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2)。意味着此时我们已经获得了 80 pts \text{80 pts} 80 pts 的好成绩。

然后这题据说是可以用一个长相清秀的单调队列来搞。

夹带私货✧⁺⸜(●˙▾˙●)⸝⁺✧关于单调队列优化DP的更多内容见此 Link

根据单调队列优化DP的套路,要维护的是 j j j 递增且 s j + h j s_j+h_j sj+hj 递增的单调队列。(由 h j < s i − s j h_jhj<sisj 变形得)

其中我们允许存在不合法的 s j + h j s_j+h_j sj+hj。因为可能 s j + h j ⩾ s i s_j+h_j\geqslant s_i sj+hjsi 但是 s j + h j < s i + 1 s_j+h_jsj+hj<si+1 并且 s j + h j s_j+h_j sj+hj 之后的都比 s i + 1 s_{i+1} si+1 大。

常规的单调队列优化,答案都在队头;

而这道题呢?因为队头可能不合法,也有可能队头,队头后面的一个元素,队头后面的后面的元素, ⋯ \cdots 都合法(并且后面几个的 j j j 值还一定比队头大),所以不能简单粗暴地通过队头查答案。

可以通过判断队头后面的一个元素是否合法,合法就删除队头的方式维护队头。(因为队列中的 j j j 递增)

因为刚才证明过,不用担心删除队头对后面的答案带来的影响。

但是,作为一只优秀的deque,怎么会有“访问队头的后面一个元素”的操作?我们稍微换个方法,用 v a l val val 记录当前满足条件的 max ⁡ { j } \max\{j\} max{j},若队头也满足条件,更新 v a l val val。最后的 max ⁡ { j } \max\{j\} max{j} 就是 v a l val val

这是队头。

队尾呢,由于塞进去的 j j j 一定递增,所以只用考虑 s j + h j s_j+h_j sj+hj 的问题。若

s j + h j s_j+h_j sj+hj 比队尾大,直接塞后面就行了;

否则,删除队尾再塞进去。

咦?删除队尾?不会影响答案吗?

记队尾元素为 r r r

删除队尾的情况意味着 r < j rr<j s r + h r ⩾ s j + h j s_r+h_r\geqslant s_j+h_j sr+hrsj+hj

如果比较大的 s r + h r s_r+h_r sr+hr 都比 s i s_i si 小,那么比较小的 s j + h j s_j+h_j sj+hj 更是比 s i s_i si 小,且 j j j 的下标更大,更优。

所以删除 r r r 毛事都不会发生。

没加__int128 88 pts \text{88 pts} 88 pts。时间复杂度 Θ ( n ) \Theta(n) Θ(n)

好的,开始加 __int128 && 卡空间。(出题人卡空间可要点B脸吧)

首先不能开 h h h d d d,用 l s t i lst_i lsti 记录 i i i 的前驱(即 i i i max ⁡ { j } \max\{j\} max{j} 的值)

然后不用单独的 s s s 算前缀和, a a a 自给自足。

我们从最后的第 n n n 块开始往回找,不断地跳到自己的前驱(也就是上一次分组的地方),直到跳到 0 0 0 为止。

注意了,从始至终都只有最后计算答案的变量 a n s ans ans 开了 __int128,其他都没有,开了会T。

好了,讲得差不多了,上代码。

Code
#include
typedef __int128 ll;
typedef long long lll;
int n,type,l,r,val;
/*略掉快读*/
namespace Task1{
    const int maxn=5e5+5;
    lll a[maxn],d[maxn],h[maxn],s[maxn],q[maxn];
    void solve(void){
        l=r=1;
        for(int i=1;i<=n;++i){
            read(a[i]);
            s[i]=s[i-1]+a[i];
            while(l<=r&&s[q[l]]+h[q[l]]<=s[i])
                val=q[l++];
            h[i]=s[i]-s[val];
            d[i]=d[val]+h[i]*h[i];
            while(l<=r&&s[q[r]]+h[q[r]]>=s[i]+h[i])
                r--;
            q[++r]=i;
        }
        printf("%lld",d[n]);
        return;
    }
}
namespace Task2{
    const lll maxn=4e7+5;
    const lll mod=1ll<<30;
    ll ans;
    lll a[maxn];
    int lst[maxn],q[maxn];
    int x,y,z,m,p,li,ri,lt;
    inline lll h(int x){return a[x]-a[lst[x]];}
    void solve(void){
        l=r=1;
        //这什么lj输入
        read(x);read(y);read(z);
        read(a[1]);read(a[2]);read(m);
        for(int i=3;i<=n;++i)
            a[i]=(x*a[i-1]+y*a[i-2]+z)%mod;
        for(int i=1;i<=m;++i){
            read(p);read(li);read(ri);
            for(int j=lt+1;j<=p;++j)
                a[j]=a[j]%(ri-li+1)+li+a[j-1];
            lt=p;
        }
        for(int i=1;i<=n;++i){
            while(l<=r&&a[q[l]]+h(q[l])<=a[i])
                val=q[l++];
            lst[i]=val;
            while(l<=r&&a[q[r]]+h(q[r])>=a[i]+h(i))
                r--;
            q[++r]=i;
        }
        while(n){
            ans+=(ll)h(n)*h(n);
            n=lst[n];
        }
        int s[31],top=0;
        while(ans){
            s[++top]=ans%10;
            ans/=10;
        }
        while(top)putchar(s[top--]+'0');
    }
}
int main(){
    #ifdef ONLINE_JUDGE
    freopen("partition.in","r",stdin);
    freopen("partition.out","w",stdout);
    #endif
    read(n);read(type);
    if(type)Task2::solve();
    else Task1::solve();
    return 0;
}

T3 树的重心

题都没看过,放弃了。


因为现在是凌晨 3 3 3 点,所以作者不想写结束语了。

end.

你可能感兴趣的:(#,CSP&NOIP,===比赛+考试==,csp)