背景:
杭州的天气够热的。
因为上传中丢失,我和
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
i−1的位置来搞(起点从
i
−
1
i-1
i−1到
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
i−1建一条权值为
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
n∗m的网格图(可以认为在坐标系上),有
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{...}
...
或许有空会补。