原题地址
【题目大意】
给定n个字符串在一个串T中出现的位置,求构造一个符合要求的字典序最小的T。
【解题思路】
暴力赋值肯定会T,重点是如何维护空位置。
我们DSU维护每个位置右边第一个空位置在哪,剩下的填a就行了。
我们还可以用线段树完成这个东西。
我们也可以用一个数组记录这个东西。
我们还可以用一些玄学加暴力搞这个东西。
【代码】
#include
const int MAXN = 2e6+5;
using namespace std;
int n,maxL;
char s[MAXN],ans[MAXN];
int main()
{
// freopen("A.in","r",stdin);
// freopen("A.out","w",stdout);
scanf("%d",&n);
while (n--)
{
int k;
scanf("%s%d",s,&k);
int len=strlen(s);
int p,start = 0;
for (int i=0;iscanf("%d",&p);
start=max(start,p);
for (int j=start;j1);
}
for(int i=1;i<=maxL;i++)
{
if (ans[i]) printf("%c",ans[i]);
else printf("a");
}
return 0;
}
【题目大意】
将n个节点构造成一棵树,树上有k个出口,使得距离最远的两个出口距离最小,出口为所有度为1的点。
【解题思路】
显然可以想到这是一颗多叉(菊花)树。
那么我们就可以把这颗树分的尽量多叉。
【代码】
#include
using namespace std;
int n,k;
int main()
{
// freopen("B.in","r",stdin);
// freopen("B.out","w",stdout);
scanf("%d%d",&n,&k);
n--;
int len1=n/k;
int len2=(n%k)?(len1+1):len1;
int ans=(n%k>=2)?(len2*2):(len1+len2);
printf("%d\n",ans);
for(int i=2;i<=k;i++)
printf("%d %d\n",1,i);
for(int i=k+1;i<=n+1;i++)
printf("%d %d\n",i-k,i);
return 0;
}
【题目大意】
给定一个只包含A,T,C,G的字符串,有如下两种操作
1)修改一个点的字母
2)给定区间L, R和一个字符串e (strlen(e) <=10),组成一个足够区间长度的由若干个e重复组成的新串,eee…,问L,R区间中有几个位置对应的字母跟这个新的字符串对应的相同。
【解题思路】
e串长度最多为10,那么对于每一个字母最多有10种不同的起始位置,10个不同的长度进行重复。建立一个4*10*10的BST维护即可。
【代码】
#include
using namespace std;
const int MAXN=1e5+10;
int len,lena,ans,q;
int id[305],tree[13][13][6][MAXN];
char s[MAXN],a[13];
inline int lowbit(int x)
{
return x&(-x);
}
inline void add(int x,int y,int z,int pos,int del)
{
while(posx][y][z][pos]+=del;
pos+=lowbit(pos);
}
}
inline int query(int x,int y,int z,int pos)
{
int ret=0;
while(pos)
{
ret+=tree[x][y][z][pos];
pos-=lowbit(pos);
}
return ret;
}
int main()
{
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
id['A']=0;id['T']=1;id['C']=2;id['G']=3;
scanf("%s",s);
len=strlen(s);
for(int i=1;i<=len;++i)
for(int j=1;j<=10;++j)
add(i%j,j,id[s[i-1]],i,1);
scanf("%d",&q);
while(q--)
{
int x,y,z;char ch[5];
scanf("%d",&x);
if(x==1)
{
scanf("%d%s",&y,ch);
for(int i=1;i<=10;++i)
{
add(y%i,i,id[s[y-1]],y,-1);
add(y%i,i,id[ch[0]],y,1);
}
s[y-1]=ch[0];
}
else
{
scanf("%d%d%s",&y,&z,a);
lena=strlen(a);ans=0;
for(int i=0;iy+i)%lena,lena,id[a[i]],z)-query((y+i)%lena,lena,id[a[i]],y-1);
printf("%d\n",ans);
}
}
return 0;
}
【题目大意】
给定一幅无向图,问每一条边的边权最大为多少,可以被包含在一颗这幅图的最小生成树内。
【解题思路】
显然要先求MST
求完之后,分两种情况讨论:
1.若一条边不在生成树上,这条边肯定与生成树上的边共同构成了一个环。如果我们想用这条边替代环上的一条边,则权值最大必须小于环上的边的最大权值。
这个用LCA+倍增简单维护一下就好了。
2.若一条边在生成树上,每个不在生成树上的边和生成树构成的环,可以先预处理出所有两端在u到v路径上的不在树上的边的最小值。它的权值一定要小于最小值。
这个还是可以倍增一下。
【代码】
#include
using namespace std;
const int INF=1e9+10;
const int MAXN=4e5+10;
const int MAXP=20;
int n,m,cnt;
int fa[MAXN],ans[MAXN],dep[MAXN],head[MAXN],vs[MAXN];
int mx[MAXP+5][MAXN],anc[MAXP+5][MAXN];
bool bo[MAXN];
struct Tway
{
int u,v,w,nex,id;
};
Tway e[MAXN<<1],d[MAXN<<1];
inline bool cmp(Tway a,Tway b)
{
return a.winline int findf(int x)
{
return x==fa[x] ? x : fa[x]=findf(fa[x]);
}
inline void add(int u,int v,int w,int id)
{
++cnt;
e[cnt].v=v;e[cnt].w=w;e[cnt].id=id;
e[cnt].nex=head[u];head[u]=cnt;
}
inline void dfs(int x,int f,int dis)
{
dep[x]=dep[f]+1;anc[0][x]=f;mx[0][x]=dis;
for(int i=1;i<=MAXP;++i)
{
anc[i][x]=anc[i-1][anc[i-1][x]];
mx[i][x]=max(mx[i-1][anc[i-1][x]],mx[i-1][x]);
}
for(int i=head[x];i;i=e[i].nex)
if(e[i].v!=f)
{
vs[e[i].v]=e[i].id;
dfs(e[i].v,x,e[i].w);
}
}
inline int lca( int x, int y, int &d )
{
d=0;
if(dep[x]for(int i=MAXP;i>=0;--i)
if(dep[anc[i][x]]>=dep[y])
{
d=max(d,mx[i][x]);
x=anc[i][x];
}
if(x==y)
return x;
for(int i =MAXP;i>=0;--i)
if(anc[i][x]!=anc[i][y])
{
d=max(d,max(mx[i][x],mx[i][y]));
x=anc[i][x];y=anc[i][y];
}
d=max(d,max(mx[0][x],mx[0][y]));
return anc[0][x];
}
inline void solve(int x,int lca,int d)
{
x=findf(x);
while(dep[x]>dep[lca])
{
ans[vs[x]]=min(ans[vs[x]],d);
int y=findf(anc[0][x]);
fa[x]=y;
x=findf(x);
}
}
int main()
{
freopen("D.in","r",stdin);
freopen("D.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
fa[i]=i;
for(int i=1;i<=m;++i)
{
scanf("%d%d%d",&d[i].u,&d[i].v,&d[i].w);
d[i].id=i;
}
sort(d+1,d+m+1,cmp);
for(int i=1;i<=m;++i)
{
int fx=findf(d[i].u),fy=findf(d[i].v);
if(fx!=fy)
{
bo[i]=true;fa[fx]=fy;
add(d[i].u,d[i].v,d[i].w,d[i].id);
add(d[i].v,d[i].u,d[i].w,d[i].id);
}
}
/* for(int i=1;i<=n;++i)
{
printf("%d:",i);
for(int j=head[i];j;j=e[j].nex)
printf("%d ",e[j].v);
printf("\n");
}*/
dfs(1,0,0);
memset(ans,63,sizeof(ans));
for(int i=1;i<=n;++i)
fa[i]=i;
for(int i=1;i<=m;++i)
if(!bo[i])
{
int u=d[i].u,v=d[i].v,f=lca(u,v,ans[d[i].id]);
ans[d[i].id]--;
solve(u,f,d[i].w-1);
solve(v,f,d[i].w-1);
}
for(int i=1;i<=m;++i)
if(ans[i]>=INF)
printf("-1 ");
else
printf("%d ",ans[i]);
return 0;
}
【题目大意】
给定一个只包含通配符’?’和’v’,’K’的串,询问所有可能的循环节长度。
【解题思路】
首先个如果x是可能的循环节,那么2x,3x也一定是。
因此可以根据这个愉快地进行暴力
暴力check每一个长度,如果可行就把它的倍数都标为可行的。
当然正解是FFT。以下为看大佬博客整理
首先根据KMP的思想,如果存在长度为k的循环节那么存在长度为(n - k)的公共前后缀。
所以我们可以把这个串右移k位然后check,最后判一下特殊情况。
特殊情况是指类似于存在某一个i使得s[i] != s[i + 2k]并且s[i + k] == ‘?’。
首先可以初步地将一些循环节判断为不可行,对于看似没有问题的循环节长度,我们还需要check它的倍数中有没有被标记为不可行的,如果存在它就不可行(这样做的话就可以把以上的特殊情况处理掉)。
接下来是正常情况。
为了更快地进行check,所以,我们设A数组中A[i]为1当且仅当s[i] == ‘v’,B[i]为1当且仅当s[i] == ‘K’。
初步可行的条件是 ∑n−k−1i=0(A[i]×B[i+k])=0 并且 ∑n−k−1i=0(B[i]×A[i+k])=0
然后为了能够顺利地进行下一步,我们设A’[i] = A[n - i - 1]。于是你会发现两边A’的下标和B的和是一个定值,而且范围不相交。因此我们可以把A’数组和B数组当成两个多项式的系数数组,然后进行FFT。
当然我不会FFT所以没写。
【代码】
#include
using namespace std;
const int MAXN=5e5+5;
int ans,len,T;
char s[MAXN];
bool flag[5],bo[MAXN];
inline bool check(int l)
{
for(int i=0;ichar c=s[i];
for(int j=i+l;jif(c!='?' && s[j]!='?' && c!=s[j])
return false;
if(s[j]!='?')
c=s[j];
}
}
return true;
}
int main()
{
freopen("E.in","r",stdin);
freopen("E.out","w",stdout);
scanf("%d",&T);
while(T--)
{
scanf("%d%s",&len,s);
flag[0]=flag[1]=false;
for(int i=0;iif(s[i]=='V')
flag[0]=true;
if(s[i]=='K')
flag[1]=true;
}
if(!flag[0] && !flag[1])
{
printf("%d\n",len);
for(int i=1;i<=len;++i)
printf("%d ",i);
printf("\n");
continue;
}
ans=0;
for(int i=1;i<=len;++i)
if(!bo[i] && check(i))
for(int j=i;j<=len;j+=i)
{
ans+=bo[j]?0:1;
bo[j]=true;
}
printf("%d\n",ans);
for(int i=1;i<=len;++i)
if(bo[i])
{
printf("%d ",i);
bo[i]=false;
}
printf("\n");
}
return 0;
}
【题目大意】
给一个无向图,其中的无向边有一些限定可以通行时间,人一开始在1号点,每一时刻他都需要不断移动,通过一条无向边的时间是1,问最早能在什么时刻到达 n 号点
【解题思路】
最简单的dp暴力是对于每一个点记录每个时间能否走到,然后转移,这样由于每条边进行了多次转移,显然会TLE。
观察到如果在第i秒来到点 S ,则第i+2秒也可以来到 S ,以此类推(当然不能超过范围)。那么可以考虑从奇偶性入手进行dp。
所以我们可以把奇偶性相同的一些时间点一起更新,将点分裂成奇数时刻和偶数时刻的点,把边拆成4条有向边:在偶数u->奇数v,奇数u->偶数v,v->u同理 。
更新的时候一次性把这条边能更新到的全部时间点都更新,就能使得边的更新数为m条 。
此时显然不能枚举时间去计算边的贡献,我们按照边产生贡献的时间顺序去计算贡献 。
为了更新每条边能贡献到的所有时刻,对于每条边需要求出一个dp[i]表示第一次到达这条边的时刻 。
对于一个点的所有能产生贡献的出边,他们产生贡献的顺序一定是按照出现时间升序的,对于每个点的出边按出现时刻升序排序。对每个点维护他的出现时间段 l[x] r[x] 。
那么我们可以:一开始从1的偶数点的第一条出边开始,每次判断当前这条边是否能产生贡献,尝试用它的 dpi 去更新这条边的结束点的时间段。
如果成功更新且结束点不在队列里,将结束点和他的当前弧放进队列,权为 (u,v) 出现后结束点当前弧出现的最早时间。
每次当前出发点的当前弧成功产生贡献或已经消失后,更新他的当前弧,将点和他新的当前弧放进队列,权也为这条边能够出现的最早时间。
【代码】
#include
using namespace std;
const int MAXN=5e5+5;
int n,m,ans;
int head[MAXN][2];
struct Tnode
{
int v,l,r;
Tnode(){}
Tnode(int vv,int ll,int rr)
{
v=vv;l=ll;r=rr;
}
friend bool operator <(Tnode A,Tnode B)
{
return A.lvectorway[MAXN][2];
struct Tp
{
int x,y;
Tp(){}
Tp(int xx,int yy)
{
x=xx;y=yy;
}
friend bool operator <(Tp A,Tp B)
{
return A.y>B.y;
}
};
priority_queueq;
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
int u,v,l,r;
scanf("%d%d%d%d",&u,&v,&l,&r);
for(int j=0;j<2;++j)
{
way[u][j].push_back(Tnode(v,l,r));
way[v][j].push_back(Tnode(u,l,r));
}
}
for(int i=1;i<=n;++i)
for(int j=0;j<2;++j)
sort(way[i][j].begin(),way[i][j].end());
}
int dij()
{
int now,tim,xx,yy;
q.push(Tp(1,0));
while(!q.empty())
{
xx=q.top().x;yy=now=q.top().y;tim=yy&1;
q.pop();
if(xx==n)
return yy;
for(int j=head[xx][tim];jif(w.l>now)
break;
head[xx][tim]=j+1;
if(w.rcontinue;
now=max(now,w.r);
if(now-tim&1)
--now;
int tmp=max(w.l,yy);
if(tmp%2 != tim)//first time: (tmp&1 != tim)
++tmp;
if(tmp+1<=w.r && tmp>=w.l)
q.push(Tp(w.v,tmp+1));
}
}
return -1;
}
int main()
{
freopen("F.in","r",stdin);
freopen("F.out","w",stdout);
init();
printf("%d\n",dij());
return 0;
}