今日得分:2+10+4
(今天的T1和T3是原题,然而我并没做过,惨遭毒瘤。。。)(不过好像大家也没太写)
(话说好像真正实现起来并没有太过毒瘤)
T1
题目大意:给你一个长度为L的字符串s0,字符集大小为2n,q次操作,A操作表示在第f个字符串s[f]后面接上一个字符x,得到一个最新的s,Q操作询问第f个字符串s[f],目前有一个字符串t已经匹配到了l,每次随机在t后面添加一个字符,期望添加多少字符使得s[f]能够作为t的子串出现mod998244353,强制在线。n,q,L<=3e5。
题解:
定义题目所求为E(s,l),通过生成函数可以证明以下式子(具体证明参考CTSC2006歌唱王国):

注意到[s[1…i]=s[n-i+1…n]]等价于1-i是s的border,于是我们考虑KMP。由于每次增加一个字符,我们可以看成在原来的结构上添加一个新节点,从而动态构建操作树。通过倍增算法可以记录每一个点的next[i]的深度以及对应链上第next[i]+1个字符,这样的话除去KMP部分的复杂度是O(NlogN)。至于KMP部分,我们考虑我们寻找next指针时,其实质相当于构建出每个点的KMP自动机,每次添加一个新字符,先通过原串的自动机找到next节点,把它的所有转移边拷贝过来,并更改一条对next节点本身的转移边,用主席树即可做到O(logn)复杂度转移。所以总时间复杂度是O(NlogN)的。
(话说为啥我写的O(nlogn)还没O(nlog^2n)跑得快啊,哭了)
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
inline int re_ad()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
return x*f;
}
const int mo=998244353;
int id;
int n,L,enc,q,tot;
int cnt,an;
struct node{int ls,rs,num;}t[18000010];
int fa[20][600010],dep[600010],Nxt[600010],rt[600010],v[600010];
int ans[600010],mc[600010];
bool read(){char ch=getchar();while(ch!='Q'&&ch!='A')ch=getchar();return ch=='Q';}
inline int query(int x,int to)
{
for(register int i=19;i>=0;--i)
{
if(dep[fa[i][x]]>=to)x=fa[i][x];
}
return x;
}
void build(int &k,int l,int r)
{
if(!k)k=++cnt;if(l==r)return;
int mid=(l+r)>>1;build(t[k].ls,l,mid);build(t[k].rs,mid+1,r);
}
int ask(int k,int l,int r,int pla)
{
if(!k)return 0;
if(l==r)return t[k].num;
int mid=(l+r)>>1;
if(pla<=mid)return ask(t[k].ls,l,mid,pla);
else return ask(t[k].rs,mid+1,r,pla);
}
void change(int &k,int las,int l,int r,int pla,int nu)
{
if(!k)k=++cnt;
if(l==r){t[k].num=nu;return;}
int mid=(l+r)>>1;
if(pla<=mid)change(t[k].ls,t[las].ls,l,mid,pla,nu),t[k].rs=t[las].rs;
else change(t[k].rs,t[las].rs,mid+1,r,pla,nu),t[k].ls=t[las].ls;
}
inline void insert(int c,int f)
{
register int i,y;
++tot;fa[0][tot]=f;dep[tot]=dep[f]+1;v[tot]=c;
for(i=1;i<=19;++i)fa[i][tot]=fa[i-1][fa[i-1][tot]];
{
Nxt[tot]=ask(rt[f],0,n,c);
y=query(tot,dep[Nxt[tot]]+1);
change(rt[tot],rt[Nxt[tot]],0,n,v[y],y);
}
ans[tot]=(ans[Nxt[tot]]+mc[dep[tot]])%mo;
}
int main()
{
freopen("rna.in","r",stdin);
freopen("rna.out","w",stdout);
register int i,j,f,l,r;
id=re_ad();
n=re_ad()<<1;L=re_ad();q=re_ad();enc=re_ad();
mc[0]=1;
for(i=1;i<=L+q;++i)mc[i]=1ll*mc[i-1]*n%mo;
build(rt[0],0,n);
for(i=1;i<=L;++i)
{
insert(re_ad(),i-1);
}
while(q--)
{
if(!read())
{
f=re_ad();r=(re_ad()+enc*an)%n;
insert(r,f+L);
}
else
{
f=re_ad();l=(re_ad()+enc*an)%(dep[f+L]+1);
an=(ans[f+L]-ans[query(f+L,l)]+mo)%mo;
printf("%d\n",an);
}
}
}
T2
题目大意:给定一个01串,进行如下操作若干轮:每一轮中,每次选择一个最靠左的并且在该串中还未选中的1,将其右侧最近的0处的位置替换为它,将其原来位置清0,如果不存在这样的1则结束这一轮,问进行无限轮之后从左到右每个极长连续1段中1的个数。
给出01串的方法为先在开头放a0个1,再放a1个0,再放a2个1,以此类推,最后再往后加无限个0。n<=1e6,1<=a<=1e9,n为偶数。
题解:我们把1看成左括号,那么每轮相当于对每一个1找到最左侧的空位补上一个右括号,且右括号的位置就是下一轮球的位置。我们把这个括号序列抽象成一个树形结构,那么有一个很好的结论:每次操作之后,这棵树中的所有长链的长度保持不变(感性理解一下)。于是我们可以维护一个左括号的栈,并维护当前这一堆左括号中从下往上数的最大深度,右括号进来的时候尝试弹栈即可。最终得到的答案需要排序,时间复杂度O(nlogn)(排序复杂度)
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
inline int re_ad()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
return x*f;
}
int n;
int z[1000010];long long f[1000010];long long ans[1000010];
int main()
{
freopen("one.in","r",stdin);
freopen("one.out","w",stdout);
register int i,x,top=0,cnt=0;
n=re_ad();
for(i=1;i<=n+1;++i)
{
x=re_ad();if(i&1){z[++top]=x;f[top]=0;continue;}
while(top&&x>=z[top])
{
if(f[top]+z[top]>=f[top-1]){if(f[top-1])ans[++cnt]=f[top-1];f[top-1]=f[top]+z[top];}
else ans[++cnt]=f[top]+z[top];
x-=z[top--];
}
if(top&&x)z[top]-=x,f[top]+=x;
}
while(top)
{
if(f[top]+z[top]>=f[top-1]){if(f[top-1])ans[++cnt]=f[top-1];f[top-1]=f[top]+z[top];}
else ans[++cnt]=f[top]+z[top];
--top;
}
if(f[0])ans[++cnt]=f[0];
sort(ans+1,ans+cnt+1);
for(i=1;i<=cnt;++i)printf("%lld ",ans[i]);
}
T3
题目大意:给你一棵n个点的树,每个点有一个点值ci,每条边有一个边权b(u,v),现在要给每个点分配一个正实数权值wi,定义点的贡献为ci*wi^2,边的贡献为b(u,v)*w[u]*w[v],求边贡献之和比上点贡献之和的最大值。n<=1e5,1<=b,c<=100。
题解:考虑二分答案,设当前考虑的答案为x,则给每个点值*x,问题变为是否存在一种方案使得边贡献之和大于等于点贡献之和。考虑把每个点的点值分配到每条边上,得到它需要分配给父节点的系数f[u]=c[u]-Σb(u,son[u])^2/(4*f[son[u]]),则最终我们只需要判断f[1]和0的大小关系即可。注意如果有一个点的f小于0,那么f1一定小于0,需要直接退出。时间复杂度O(nlogK)(logK为实数二分)
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
inline int re_ad()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
return x*f;
}
const double eps=1e-12;
int n;
double a[100010],w[100010];
struct node
{
int to,cost;
};
vector<node> g[100010];
double num[100010];
double ans;
bool dfs(int x,int fa)
{
register int i,sz=g[x].size(),v;
for(i=0;i<sz;++i)
{
v=g[x][i].to;if(v==fa)continue;
if(!dfs(v,x))return false;
w[x]-=1.0*g[x][i].cost*g[x][i].cost/(4.0*w[v]);
}
return w[x]>=-eps;
}
inline bool check(double x)
{
for(register int i=1;i<=n;++i)w[i]=a[i]*x;
return dfs(1,0);
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
register int i,j,x,y,z;
n=re_ad();
for(i=1;i<=n;++i)a[i]=re_ad();
for(i=1;i<n;++i)
{
x=re_ad();y=re_ad();z=re_ad();g[x].push_back((node){y,z});g[y].push_back((node){x,z});
}
register double l=eps,r=1e7,mid;
while(r-l>eps)
{
mid=(l+r)/2.0;
if(check(mid)){r=ans=mid;}
else l=mid;
}
printf("%.10lf\n",ans);
return 0;
}
博客分享了三道算法竞赛题目(T1、T2、T3)的详细解析和AC代码。T1涉及字符串处理,利用KMP动态构建操作树解决;T2讨论01串的变换,转化为括号序列并用栈维护;T3是关于树的权值分配问题,采用二分答案策略。每道题目的解决方案都包括复杂度分析。
1469

被折叠的 条评论
为什么被折叠?



