DAY 8

博客分享了三道算法竞赛题目(T1、T2、T3)的详细解析和AC代码。T1涉及字符串处理,利用KMP动态构建操作树解决;T2讨论01串的变换,转化为括号序列并用栈维护;T3是关于树的权值分配问题,采用二分答案策略。每道题目的解决方案都包括复杂度分析。

今日得分: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;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值