SMSC2021 Day5&Day6 部分题解

Day5

T1 矩阵 matrix (※差分)

在这里插入图片描述
差分技巧在区间操作上的应用。
对于一段序列 { a i } \{a_i\} {ai} ,我们如果对下标处于 [ l , r ] [l,r] [l,r] 区间的所有元素增加 s s s,我们可以考虑 O ( 1 ) O(1) O(1) 记录操作。新序列 { b i } \{b_i\} {bi} 初始时全部元素为 0 0 0 ,在进行一次区间操作后,记录 b l ← b l + s , b r + 1 ← b r + 1 − s b_l\leftarrow b_l + s,b_{r+1}\leftarrow b_{r+1}-s blbl+s,br+1br+1s,如有若干次操作,亦同上处理。最后询问操作后的序列 { a i ′ } \{a'_i\} {ai},那么对 { b i } \{b_i\} {bi} 做一次前缀和得到 { b i ′ } \{b'_i\} {bi} ,就有 a i ′ = a i + b i ′ a'_i=a_i+b'_i ai=ai+bi
差分可以高效地解决“多次区间操作,最终单次询问”的问题,步骤概括如下:

  1. 开一个差分数组 b[],每次接收操作 [ l , r ] + s [l,r]+s [l,r]+s,则 b[l] += s , b[r+1] -= s
  2. 最终询问时,先对差分数组做前缀和操作 bs[i] = bs[i-1] + b[i]
  3. 最终得到答案 ans[i] = a[i] + bs[i]

回到本题,我们当然可以考虑每一次操作枚举行数 r ′ r' r ,然后给每一行打上标记,但是这样复杂度在询问次数 q q q 比较大时太大,所以要考虑改进这个 O ( n q ) O(nq) O(nq) 的算法。
考虑上述算法的过程,如下图:
在这里插入图片描述
我们发现,在纵向也出现了区间操作,显然可以考虑在纵向对已有的差分标记再进行差分。对已有的差分数组b1[][]再进行纵向差分的操作较为简单,但对其进行斜向差分的操作则不显而易见,但是我们仍然可以考虑设置一个新的斜向差分的数组 b2[][],计算最终表格时先将 b1[][]纵向差分前缀和处理,再将 b1[][]横向差分前缀和处理,最后对斜向差分数组 b2[][]斜向前缀和处理,将所有差分数组相加即得到某个位置的元素。
在这里插入图片描述

#include<iostream>
#include<cstdio>

using namespace std;
typedef long long ll;
int n,q,row,col,len,sum;
ll map[2005][2005],tmp,ans;
ll tag[2005][2005],ttag[2005][2005];


int main()
{
	scanf("%d%d",&n,&q);
	for(int t = 1;t <= q;t ++)
	{
		scanf("%d%d%d%d",&row,&col,&len,&sum);
		tag[row][col] += sum;
		ttag[row][col+1] -= sum;
		tag[min(row+len,n+1)][col] -= sum;
		ttag[min(n+1,row+len)][min(col+len+1,n+1)] += sum;
	}
	
	for(int i = 1;i <= n;i ++)
	{
		tmp = 0;
		for(int j = 1;j <= n;j ++)
		{
			tmp += tag[i][j] + ttag[i][j];
			map[i][j] = tmp;
			tag[i + 1][j] += tag[i][j];
			ttag[i + 1][j + 1] += ttag[i][j];
		}
	}
	for(int i = 1;i <= n;i ++)
		for(int j = 1;j <= n;j ++)
			ans = ans^map[i][j];
	printf("%lld",ans);
	return 0;
}

Day6

T1 旅行 travel (※差分,树上二分与倍增)

在这里插入图片描述
可以将本题概括为“树上结点覆盖问题”,有多次操作单次最终询问的特点,可以考虑使用差分,具体操作如下:

  1. 对于每一个点 i i i 找出它能向上跳的最远的祖先 j j j
  2. i i i 的父亲为 fa ⁡ ( i ) \operatorname {fa}(i) fa(i),差分标记 b[fa(i)] ++,b[fa(j)] ++
  3. 最后统计答案时是从叶子结点回溯到根结点,且过程中 bs ⁡ ( u ) = ∑ i ∈ { son ⁡ ( u ) } bs ⁡ ( i ) \operatorname{bs}(u) = \sum_{i\in \{\operatorname{son}(u)\}}\operatorname{bs}(i) bs(u)=i{son(u)}bs(i)bs[u] += bs[son[u][i]]

在这里插入图片描述
至于向上跳的过程可以倍增预处理祖先和与祖先之间的距离。

#include<iostream>
#include<cstdio>

using namespace std;
typedef long long ll;
int n;
ll d[200005],y;
int x,head[200005],cnt;
int f[200005][25];
ll di[200005][25];
int ans[200005];
struct edge{
	int nxt;
	int to;
	ll dis;
}e[500005];

void prework(int u,int fa)
{
	for(int i = 1;i <= 20;i ++)
	{
		f[u][i] = f[f[u][i-1]][i-1];
		di[u][i] = di[u][i-1]+di[f[u][i-1]][i-1];
	}
	int v = 0;
	for(int i = head[u];i;i = e[i].nxt)
	{
		v = e[i].to;
		if(v == fa) continue;
		f[v][0] = u;di[v][0] = e[i].dis;
		prework(v,u);
	}
	return ;
}

void addedge(int from,int to,ll dis)
{
	cnt ++;
	e[cnt].nxt = head[from];
	e[cnt].to = to;
	e[cnt].dis = dis;
	head[from] = cnt;
	cnt ++;
	e[cnt].nxt = head[to];
	e[cnt].to = from;
	e[cnt].dis = dis;
	head[to] = cnt;
	return ;
}

void dfs(int u)
{
	if(u > 1 && d[u] >= di[u][0])
	{
		int x = u;int tmp = 0;
		for(int i = 20;i >= 0;i --)
		{
			if(tmp + di[x][i] <= d[u])
			{
				tmp += di[x][i];
				x = f[x][i];
			}
		}
		ans[f[u][0]] ++;ans[f[x][0]] --;
	}
	
	for(int i = head[u];i;i = e[i].nxt)
	{
		if(f[u][0] != e[i].to)
		{
			dfs(e[i].to);
			ans[u] += ans[e[i].to];
		}
	}
	return ;
}

int main()
{
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++)
		scanf("%lld",&d[i]);
	for(int i = 2;i <= n;i ++)
	{
		scanf("%d%lld",&x,&y);
		addedge(i,x,y);
	}
	prework(1,0);
	dfs(1);
	for(int i = 1;i <= n;i ++)
		printf("%d\n",ans[i]);
	return 0;
}

T2 串串串 string (二分答案,状压 DP)

CF1550E Stringforces
在这里插入图片描述
在这里插入图片描述

题目中有明显字眼提示求最大的最小值,果断考虑二分答案,显然要二分字符串的价值。字符串的价值可表示为 valstr ⁡ s = min ⁡ { maxlen ⁡ ( c i ) } \operatorname{valstr}_s=\min\{\operatorname{maxlen}(c_i)\} valstrs=min{maxlen(ci)},也就意味着如果我们二分到一个字符串的价值为 m i d mid mid 那么其中前 k k k 个字母都必须至少出现 m i d mid mid 次,问题就在于如何判断给出的字符串能否满足以上条件。
观察到数据范围 k ⩽ 17 k\leqslant 17 k17 ,存在状压 DP 的可能性,但问题是如何设置动态规划的状态从而判断字符串满足条件。对于判定构造的存在性,我们不妨先转换一下思路,可以考虑将其转换为 DP 能做的计数问题或最优化问题,如计数符合要求的构造数目或者符合要求的构造的最小前缀。那么这里采用的是后者,我们可以考虑设 f ( S ) f(S) f(S) 表示满足前 k k k 个字符是否出现 m i d mid mid 次的状态为 S S S 的最小位置,即 f ( 101 ) = 5 f(101)=5 f(101)=5 可以表示最早在字符串的前 5 5 5 位,可以满足第一个和第三个字母出现 m i d mid mid 次,但第二个字母出现不足 m i d mid mid 次。时间复杂度 O ( 2 k ⋅ k ) O(2^k\cdot k) O(2kk)
接下来的问题就变为如何转移,显然要知道前 k k k 个字母都在哪些位置可能连续出现 m i d mid mid 次,这时只需在 DP 前做一次预处理即可。为了降低程序运行的时间复杂度,我们可以预处理出对于某个字母在位置 i i i 时,离这个位置最近的转移位置。一种比较简便的办法是枚举每种字母,从后向前扫,如果?或此字母的出现次数大于 m i d mid mid 就记录转移位置或者转移后所在的位置,预处理时间复杂度 O ( n k ) O(nk) O(nk)
总时间复杂度 O ( ( n k + 2 k ⋅ k ) log ⁡ n ) O((nk+2^k\cdot k)\log n) O((nk+2kk)logn)

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>

using namespace std;
int n,k,lt,rt,mid,pos[20][200005],tmp,nxt,fir_pos,pre;
string s,stri[25],tmpstr;
char str[200005],tmpc;
bool flag1,able[20];
int f[1050000],tot,sta,ans;

void GetPos()
{
	for(int i = 1;i <= k;i ++)
	{
		tmp = 0;pos[i][n + 3] = pos[i][n + 1] = pos[i][n + 2] = n + 2; 
		for(int j = n;j >= 1;j --)
		{
			if(str[j] == i+'a'-1 || str[j]  == '?') tmp ++;
			else tmp = 0;
			if(tmp >= mid) pos[i][j] = j + mid - 1;
			else pos[i][j] = pos[i][j + 1];
		}
	}
	return ;
}

bool DP()
{
	tot = ( 1 << k ) - 1 ;
	for(int i = 1;i <= tot;i ++) f[i] = n + 2;
	f[0] = 0;
	for(int i = 1;i <= tot;i ++)
		for(int j = 1;j <= k;j ++)
			if((i&(1<<(j-1))) > 0)
			{
				sta = i - (1<<(j-1));
				f[i] = min(f[i],pos[j][f[sta] + 1]);
			}
	if(f[tot] > n) return 0;
	else return 1;
}

int main()
{
	scanf("%d%d",&n,&k);
	cin >> s;
	for(int i = 0;i < s.size();i ++) str[i + 1] = s[i]; 
	lt = 1;rt = n;
	while(lt <= rt)
	{
		mid = (lt+rt)>>1;
		GetPos();
		if(DP())
		{
			ans = mid;
			lt = mid + 1;
		}
		else rt = mid - 1;
	}
	printf("%d",ans);
	return 0 ;
}

T3 网格流 grim (网络流,最小割)

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值