杭二学习Day2——比赛

背景:

杭州的天气够热的。
因为上传中丢失,我和 brz \text{brz} brz没有成绩。

T1 \text{T1} T1

que \text{que} que

给定一个字符串 s s s,求这个字符串的一个子串满足:
1. 1. 1.每一个出现过的字符恰好出现一次;
2. 2. 2.在满足 1 1 1的条件下字典序最小。

sol \text{sol} sol

没用多久就想出了 Θ ( n 2 ) \Theta(n^2) Θ(n2)的暴力。
对于每一个字符 i i i,我们找到其在最末尾的位置,记为 e n d i end_i endi
对于每一个位置 i i i,我们找到其在下一个和 s i s_i si相同且不连续的位置,记为 n e x t i next_i nexti
对于每一个位置 i i i,我们计算从这个位置开始的后缀是否可以满足条件 [ 1 ] [1] [1],记为 o k i ok_i oki

一种贪心策略是从所有位置找到一个 s i s_i si的字典序最小的 o k i = 1 ok_i=1 oki=1的位置 i i i,且 i i i尽可能小。然后钦定它为最后结果的开头。
[ 1 ] . [1]. [1].若一些字符只在 i i i n e x t i next_i nexti之间出现,则只能 i + + i++ i++
[ 2 ] . [2]. [2].若未出现的字符在 i i i n e x t i next_i nexti之间的字典序更小(相对于 n e x t i next_i nexti之后的未出现的字符的字典序),则必须 i + + i++ i++
[ 3 ] . [3]. [3].若未出现的字符在 i i i n e x t i next_i nexti之间的字典序更大(相对于 n e x t i next_i nexti之后的未出现的字符的字典序),则判断 n e x t i next_i nexti之后能否出现未出现的全部字符,若可以,则 i = n e c t i i=nect_i i=necti,反之 i + + i++ i++
仔细思考,正确性显然。
然后你优化一下,就可以到 Θ ( n ∗ 字 符 集 大 小 ) \Theta(n*字符集大小) Θ(n)

code \text{code} code
考场上的。
只放了求解 e n d , n e x t , o k end,next,ok end,next,ok的部分。
l a s t last last来转移 n e x t next next可以降到 Θ ( n ) \Theta(n) Θ(n)
o k ok ok则可以用 i − 1 i-1 i1的位置来搞(起点从 i − 1 i-1 i1 i i i只影响这一位)。

	for(int i=1;i<=l;i++)
	{
		if(!flag[s[i]-'a'+1]) tot++,flag[s[i]-'a'+1]=true;
		last[i]=end[s[i]-'a'+1];
		end[s[i]-'a'+1]=i;
	}
	for(int i=1;i<=l;i++)
		if(last[i]) next[last[i]]=i;
	for(int i=1;i<=l;i++)
		while(i+1==next[i]) next[i]=next[i+1];
	ok[1]=true;
	for(int i=2;i<=l;i++)
	{
		if(end[s[i-1]-'a'+1]==i-1) break;
		ok[i]=true;
	}


T2 \text{T2} T2

que \text{que} que

在输出框输入一个数。
有三种操作:
[ 1 ] . [1]. [1].把所有输入框内的内容复制到剪贴版;
[ 2 ] . [2]. [2].把剪贴版里的内容全部粘贴一遍(注意,这个操作不会改变剪贴版里的东西);
[ 3 ] . [3]. [3].把输入框里的最后那一个数删除。
定义 v a l i val_i vali表示使得最后输出框的数的个数为 i i i的最小操作数。求 1 1 1 n n n v a l val val序列。

sol \text{sol} sol

考场上敲了一份暴力, 20 pts 20\text{pts} 20pts的,发现一些性质后感觉打表能水到 40 pts 40\text{pts} 40pts
于是开始打表。
可是在做到 901 901 901的时候,炸了,黑屏了,死机了,重启了,表没存啊。
那就只能交了一份暴力。

//x,y,d,zhuangtai分别表示输出框的数的个数,剪贴板的数的个数,当前操作数,当前做哪一步操作
f.push((node){1,0,0,-1});
while(!f.empty())
{
	node NOW=f.front();
	if(NOW.x==x) return NOW.d;
	f.pop();
	if(NOW.zhuangtai)//不存在复制两次
		f.push((node){NOW.x,NOW.x,NOW.d+1,0});
	f.push((node){NOW.x+NOW.y,NOW.y,NOW.d+1,1});
	if(NOW.zhuangtai!=2)//不存在删除两次,因为删除一定在复制或粘贴后,复制或粘贴后删除两次共要3步,而先删除一次,再复制,再粘贴也要3步
		f.push((node){NOW.x-1,NOW.y,NOW.d+1,2});
}

以上都是废话。
因为有删除,没法 dp \text{dp} dp
正解你考虑建图表示这三种状态。
若从 i i i转移到 2 i 2i 2i,那么操作数为 3 3 3次(复制后粘贴),从 i i i转移到 3 i 3i 3i,那么操作数为 3 3 3次(复制后粘贴后粘贴),…,以此类推。建操作次数条边。
删除就相当于从 i i i i − 1 i-1 i1建一条权值为 1 1 1的边。
可能会卡常?
有一个很好的性质,只有 i i i质数的倍转移才需要建边。
假设要做 6 6 6,从 1 1 1过来要复制后粘贴 5 5 5遍,共 6 6 6步;从 2 2 2转移则是复制后粘贴,复制后粘贴后粘贴,共 5 5 5步。
这样的时间复杂度大概就是线性的了。

code \text{code} code

update \text{update} update 2019.8.8 2019.8.8 2019.8.8
极限数据开 O2 3s \text{O2 3s} O2 3s,把 STL \text{STL} STL写成普通实现+大力卡常应该过吧。
主要是 n n n太大了。

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define LL long long
#define mod 998244353
#define R register
#define I inline
using namespace std;
queue<int> f;
	int n,t=0,len=0,ans=0;
	struct node{int y,z,next;} a[10000010];
	int prime[2000010],dis[2000010],last[2000010],POW[2000010];
	bool bz[2000010];
I void ins(int x,int y,int z)
{
	a[++len]=(node){y,z,last[x]}; last[x]=len;
}
I void init()
{
	bz[0]=bz[1]=true;
	for(R int i=2;i<=n;i++)
	{
		if(!bz[i]) prime[++t]=i;
		for(R int j=1;j<=t&&prime[j]*i<=n;j++)
		{
			bz[i*prime[j]]=true;
			if(!(i%prime[j])) break;
		}
	}
}
I void spfa()
{
	memset(dis,63,sizeof(dis));
	dis[1]=0;
	memset(bz,false,sizeof(bz));
	bz[1]=true;
	f.push(1);
	while(!f.empty())
	{
		int x=f.front();
		f.pop();
		for(R int i=last[x];i;i=a[i].next)
		{
			int y=a[i].y;
			if(dis[x]+a[i].z<dis[y])
			{
				dis[y]=dis[x]+a[i].z;
				if(!bz[y]) bz[y]=true,f.push(y);
			}
		}
		bz[x]=false;
	}
}
int main()
{
	scanf("%d",&n);
	init();
	for(R int i=1;i<n;i++)
	{
		for(R int j=1;j<=t&&i*prime[j]<=n;j++)
			ins(i,i*prime[j],prime[j]);
		ins(i+1,i,1);
	}
	spfa();
	POW[0]=1;
	for(R int i=1;i<=n;i++)
		POW[i]=(LL)POW[i-1]*19260817%mod;
	for(R int i=1;i<=n;i++)
		ans=((LL)ans+(LL)dis[i]*POW[n-i]%mod)%mod;
	printf("%d",ans);
}


T3 \text{T3} T3

que \text{que} que

n ∗ m n*m nm的网格图(可以认为在坐标系上),有 k k k个片障碍,给定这些障碍的左上,右下坐标。现在只能向右和向上走,求从 ( 1 , 1 ) (1,1) (1,1)(左下)到 ( n , m ) (n,m) (n,m)(右上)的可行方案数。

sol \text{sol} sol

考场上只剩下 10 min 10\text{min} 10min想这道题了。
定义 f i , j f_{i,j} fi,j表示当前到第 i i i列,最下面的那一条线在第 j j j行的方案数。
主要是这个定义不好想,因为你不好设计状态表示可行的方案数。
当遇到一个障碍时,我们从障碍上边界所在行开始往下延申,直到遇到另一个障碍或者整张图的下端点,把这一段的答案加入上边界上方的一行,再障碍所占据的那些行清空。
在这里插入图片描述
上面那个过程要区间置0,区间询问,单点修改,可以用线段树维护,然后一列一列扫过去更新即可。时间复杂度: Θ ( n log ⁡ m ) \Theta(n\log m) Θ(nlogm)

可是 n n n非常大。
发现空地对答案不会有影响,有影响的只是每一片障碍的左边的那一条线,因此你找到有用的障碍左边的那一条线,一条一条过下去即可。时间复杂度: Θ ( k log ⁡ m ) \Theta(k\log m) Θ(klogm)

code \text{code} code

咕咕咕 ... \text{...} ...
或许有空会补。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值