[SDOI2017]硬币游戏
[SDOI2017]天才黑客
题目中的代价与这次走的边和上次走的边有关。
显然可以这样建边:
设f[x][i]表示x节点上次走的边为i的最小代价。
f[x][i]向x有边的节点f[y][j]连边。
两条边的代价是trie树上的lca
但是显然无法ac。
考虑换一种建图方式。
把一条边拆成两个点:出点和入点,点之间连权值为边权的边
对于两条边(a,b),(c,d)来讲,如果b=c,那么第一个边的出点向第二个边的入点连一条权值为dep[lca]的边。
但是显然也无法通过。
这样子建图点数是原图的边数,无法优化,可以考虑优化新图的边数。
把一个点的所有入/出点按照lca排序,新建一排点表示每条边。
如果我们按照sa那样求height数组表示相邻两个串的lca的深度,则每条边的代价就是它对应的区间的height数组的min值。
考虑一个点的贡献,是一个区间,用单调栈处理然后线段树优化连边,时间复杂度为两个log。
考虑继续进行优化。新建4排点:a1,a2,b1,b2,a1,a1用于处理前缀,b1,b2用于处理后缀。
设b[i]表示所有被排序的点的第i个。
还是把每条边拆成一个点。
a1,a2上的每个点i向i-1连边,b1,b2的每个点i->i+1连边。
现在处理前缀连边,注意到每条边的意义是:i节点可以通过dep[lca(b[i],b[i-1])]的代价访问到前面的点。所以a1的第i个点向a2的第i-1个点连边。
处理b1,b2同理。
最终把a2,b2向原来的边连边权为0的边即可。
在跑最短路时要加上原来的边权。
时间复杂度mlogm,常数较大。
#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define int long long
int n,m,k,d[N],a,b,c[N],f[N][20],de[N],h[N],v[N],nxt[N],ec,id[N],cc,p1[N],p2[N],s1[N],s2[N],va[N],c1,ct,dd[N],vi[N],cv;
struct no{
int x,d;
};
int operator <(no x,no y){
return x.d>y.d;
}
int cp(int x,int y){
return id[d[abs(x)]]<id[d[abs(y)]];
}
vector<no>g[N];
vector<int>in[N],ou[N];
priority_queue<no>q;
void add(int x,int y){v[++ec]=y;nxt[ec]=h[x];h[x]=ec;}
void adj(int x,int y,int z){g[x].push_back((no){y,z});}
void dfs(int x,int fa){
f[x][0]=fa;
id[x]=++cc;
for(int i=h[x];i;i=nxt[i])
if(v[i]!=fa){
de[v[i]]=de[x]+1;
dfs(v[i],x);
}
}
int lc(int x,int y){
if(de[x]>de[y])swap(x,y);
for(int i=19;~i;i--)
if(de[f[y][i]]>=de[x])
y=f[y][i];
if(x==y)return x;
for(int i=19;~i;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int dij(){
while(!q.empty()){
no x=q.top();
q.pop();
if(vi[x.x])continue;
vi[x.x]=1;
for(int i=0;i<g[x.x].size();i++)
if(dd[g[x.x][i].x]>dd[x.x]+g[x.x][i].d+c[g[x.x][i].x]){
dd[g[x.x][i].x]=dd[x.x]+g[x.x][i].d+c[g[x.x][i].x];
q.push((no){g[x.x][i].x,dd[g[x.x][i].x]});
}
}
}
signed main(){
//freopen("r.in","r",stdin);
//freopen("w.out","w",stdout);
int t;
cin>>t;
while(t--){
cin>>n>>m>>k;
memset(dd,63,sizeof(dd));
memset(c,0,sizeof(c));
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y>>c[i]>>d[i];
in[y].push_back(i);
ou[x].push_back(i);
if(x==1){
q.push((no){i,c[i]});
dd[i]=c[i];
}
}
for(int i=1;i<k;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y);
add(y,x);
}
dfs(1,0);
f[1][0]=1;
for(int i=1;i<20;i++)
for(int j=1;j<=k;j++)
f[j][i]=f[f[j][i-1]][i-1];
ct=m;
for(int i=1;i<=n;i++){
c1=0;
for(int j=0;j<in[i].size();j++)
va[++c1]=in[i][j];
for(int j=0;j<ou[i].size();j++)
va[++c1]=-ou[i][j];
sort(va+1,va+c1+1,cp);
for(int j=1;j<=c1;j++){
p1[j]=++ct;
s1[j]=++ct;
p2[j]=++ct;
s2[j]=++ct;
}
for(int j=1;j<=c1;j++){
if(j>1){
adj(p1[j],p1[j-1],0);
adj(p2[j],p2[j-1],0);
}
if(j<c1){
adj(s1[j],s1[j+1],0);
adj(s2[j],s2[j+1],0);
}
if(va[j]>0){
adj(va[j],p1[j],0);
adj(va[j],s1[j],0);
}
else{
adj(s2[j],-va[j],0);
adj(p2[j],-va[j],0);
}
}
for(int j=2;j<=c1;j++){
int v=de[lc(d[abs(va[j])],d[abs(va[j-1])])];
adj(p1[j],p2[j-1],v);
}
for(int j=1;j<c1;j++){
int v=de[lc(d[abs(va[j])],d[abs(va[j+1])])];
adj(s1[j],s2[j+1],v);
}
}
dij();
for(int i=2;i<=n;i++){
int r=1e18;
for(int j=0;j<in[i].size();j++)
r=min(r,dd[in[i][j]]);
cout<<r<<'
';
}
ec=cc=ct=0;
memset(h,0,sizeof(h));
memset(vi,0,sizeof(vi));
for(int i=0;i<N;i++){
in[i].clear();
ou[i].clear();
g[i].clear();
}
}
}
[SDOI2017]新生舞会
简单题,直接分数规划后费用流即可
[SDOI2017]遗忘的集合
[SDOI2017]相关分析
[SDOI2017]数字表格
[SDOI2017]文本校正
分类讨论。文本串共有3!种拼接方式。
如果原串abc代表新串abc,直接判断。
如果原串abc代表新串bca。
原串a|bc代表新串bc|a
枚举a的长度,分两端哈希。
如果原串abc代表新串cab,可以仿照上面一种情况做。
如果原串abc代表新串cba。
考虑把T倒序插入S
即生成新的串(ss=s_1t_ns_2t_n-1...s_nt_1)
如果一个长度为偶数,开头坐标为奇数的串[l,r]是回文串。
如果a为ss[l]在s中对应的编号,b在ss[r]在s中对应的编号,中间有k个属于t的字符。
则(s[a...a+k-1]==t[b-k+1...b])。
所以问题就是判定ss是否能够划分为3个回文串。
manacher一下原串。枚举第一个回文串,接下来就是判断双回文串。
根据jcvb字符串课件,一个双回文串st一定能表示成ab,其中a为st的最长回文前缀或者b为st的最长回文后缀。
设(f[i])表示以i开头的最长回文串,(g[i])表示以i结尾的最长回文串,可以dp解决,具体看代码。
如果前面的回文串是([1,i]),且则需要判定(ss[i...n*2])是否为双回文串。
要判定(a)是否是(ss[i...n*2])的最长回文前缀,则要判定(ss[f[i]+1...n*2])是否是回文串。manacher处理。
要判定(b)是否是(ss[i...n*2])的最长回文后缀,使用一个队列(q)存储(ss[1...n*2])的所有回文后缀开始的位置(i).
如果(q)的头部(<i),则删除(q)的头部。
删完以后(q)的头部(x)就是(ss[i...n*2])的最长回文后缀。
则判定(ss[i...x-1])是否是回文串即可。
如果原串(abc)代表新串(acb)
考虑倒着枚举(a)的长度,则题目要求每次在开头插入一个字符/判断是否合法。
可以按照上面的方法做。
但是每次开头的(ss[2])都会变化,不太能做。
注意到我们寻找最长双回文串的过程,等价于寻找最大的p,使得(s[i...p]==t[p+1,n]),使用哈希计算剩余的部分即可。
如果原串(abc)代表新串(bac),可以仿照上面一种情况做。
代码很难写。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 2000010
#define mo 998244353
#define bb 1000001
int T,n,m,s[N],t[N],s1[N],t1[N],pr[N],sf[N],ss[N],tp;
struct hs{
int n,bs[N],hs[N];
void gt(int *a,int m){
n=m;
bs[0]=1;
for(int i=1;i<=m;i++)
bs[i]=bs[i-1]*bb%mo;
for(int i=1;i<=m;i++)
hs[i]=(hs[i-1]+a[i]*bs[i])%mo;
}
int gg(int l,int r){
return (hs[r]-hs[l-1]+mo)%mo*bs[n-r]%mo;
}
void cl(){
for(int i=1;i<=n;i++)
bs[i]=hs[i]=0;
n=0;
}
}a,b;
struct km{
int n,f[N],b[N];
void gt(int *a,int m){
n=m;
for(int i=1;i<=m;i++)
b[i]=a[i];
int j=0;
for(int i=2;i<=m;i++){
while(j&&b[j+1]!=b[i])
j=f[j];
if(b[j+1]==b[i])j++;
f[i]=j;
}
}
void gg(int &p,int c){
while(p&&b[p+1]!=c)
p=f[p];
if(b[p+1]==c)
p++;
}
void cl(){
for(int i=1;i<=n;i++)
b[i]=f[i]=0;
n=0;
}
}c,d;
struct pl{
int n,f[N],b[N];
void gt(int *a,int m){
n=m;
for(int i=1;i<=n;i++)
b[i]=a[i];
int p=0,md=0;
b[m+1]=1e9;
for(int i=1;i<=n;i++){
if(p>i)
f[i]=min(p-i,f[md*2-i]);
else f[i]=0;
while(i>f[i]&&i+f[i]+1<=n&&b[i-f[i]]==b[i+f[i]+1])
f[i]++;
if(i+f[i]>p){
p=i+f[i];
md=i;
}
}
}
int gg(int l,int r){
if((r-l+1)&1)return 0;
int lv=(r-l+1)/2,le=l+lv-1;
if(f[le]>=lv)return 1;
return 0;
}
void cl(){
for(int i=1;i<=n+1;i++)
f[i]=b[i]=0;
n=0;
}
}e;
signed main(){
//freopen("r.in","r",stdin);
cin>>T;
while(T--){
a.cl();
b.cl();
c.cl();
d.cl();
e.cl();
cin>>n>>m;
int ok=1,o1=1;
for(int i=1;i<=n;i++)
scanf("%lld",&s[i]);
for(int i=1;i<=n;i++){
scanf("%lld",&t[i]);
if(s[i]!=t[i])o1=0;
}
if(o1){
puts("YES");
continue;
}
o1=0;
a.gt(s,n);
b.gt(t,n);
for(int i=1;i<n;i++)//abc bca
if(!o1&&a.gg(1,i)==b.gg(n-i+1,n)&&a.gg(i+1,n)==b.gg(1,n-i)){
puts("YES");
o1=1;
}
for(int i=2;i<=n;i++)//abc cab
if(!o1&&a.gg(1,i)==b.gg(n-i+1,n)&&a.gg(i+1,n)==b.gg(1,n-i)){
puts("YES");
o1=1;
}
for(int i=1;i<=n;i++){
s1[i]=s[n-i+1];
t1[i]=t[n-i+1];
}
c.gt(s1,n);
d.gt(t1,n);
int p1=0,p2=0;
for(int i=n;i>1;i--){//abc acb
c.gg(p1,t[i]);
d.gg(p2,s[i]);
if(p2&&!o1&&a.gg(1,i-1)==b.gg(1,i-1)&&a.gg(i+p2,n)==b.gg(i,n-p2)){
puts("YES");
o1=1;
}
if(p1&&!o1&&a.gg(1,i-1)==b.gg(1,i-1)&&a.gg(i,n-p1)==b.gg(i+p1,n)){
o1=1;
puts("YES");
}
}
c.cl();
d.cl();
c.gt(s,n);
d.gt(t,n);
p1=p2=0;
for(int i=1;i<n;i++){//abc bac
c.gg(p1,t[i]);
d.gg(p2,s[i]);
if(!o1&&a.gg(i+1,n)==b.gg(i+1,n)&&a.gg(p1+1,i)==b.gg(1,i-p1)){
puts("YES");
o1=1;
}
if(!o1&&a.gg(i+1,n)==b.gg(i+1,n)&&a.gg(1,i-p2)==b.gg(p2+1,i)){
puts("YES");
o1=1;
}
}
p1=p2=1;
for(int i=1;i<=2*n;i++){
if(i%2==1){
s1[i]=s[p1];
p1++;
}
else{
s1[i]=t[n-p2+1];
p2++;
}
}
e.gt(s1,n*2);
for(int i=1;i<=n*2;i++)
sf[i]=pr[i]=i;
for(int i=1;i<=n*2;i++){
pr[i-e.f[i]+1]=max(pr[i-e.f[i]+1],i+e.f[i]);
sf[i+e.f[i]]=min(sf[i+e.f[i]],i-e.f[i]+1);
}
for(int i=2;i<=n*2;i++)
pr[i]=max(pr[i],pr[i-1]-1);
for(int i=n*2-1;i;i--)
sf[i]=min(sf[i],sf[i+1]+1);
tp=0;
for(int i=1;i<=n*2;i++)
if(pr[i]==n*2)ss[++tp]=i;
int po=1;
if(T==2){
T++;
T--;
}
for(int i=2;i<=n*2;i+=2)
if(sf[i]==1){
int l=i+1,r=pr[i+1];
if(!o1&&e.gg(r+1,n*2)){
puts("YES");
o1=1;
}
while(ss[po]<=i)po--;
l=ss[po];
r=n;
if(!o1&&e.gg(i+1,l-1)&&po){
puts("YES");
o1=1;
}
}
if(!o1)puts("NO");
for(int i=1;i<=2*n;i++)
pr[i]=sf[i]=0;
}
}
[SDOI2017]苹果树
[SDOI2017]龙与地下城
https://www.cnblogs.com/cszmc2004/p/12963732.html
[SDOI2017]树点涂色
用lct维护颜色相同的连续段。
如果把每个节点x的重儿子p[x]设为和它颜色相同的点的编号。
则一次染色操作就是把x->根节点的点access一下,使用重链把这些点连起来。
如果设f[x]表示x->根节点的虚边个数。
由于每次染的颜色都是不同的,所以f[x]就是x节点到根节点的色数。
使用一个线段树st。
如果一条重边->虚边,则子树dfs序+1否则-1。
查询就是区间最大值。
[SDOI2017]序列计数
[SDOI2017]切树游戏