C - Family View
题意:
给你
n
n
n个模式串,模式串中只有小写字母,模式串长度之和
l
e
n
<
=
1
e
6
len<=1e6
len<=1e6,然后给你一个匹配串
l
e
n
<
=
1
e
6
len<=1e6
len<=1e6,匹配串由全字符集组成。要求将匹配串中的所有和模式串匹配的子段替换成 ‘*’ ;
思路:
优化AC自动机;普通的AC自动机在匹配的时候会暴力跳fail指针;也有建fail 树来统计每个匹配串在文本串中出现的次数的优化。但是这道题我们只用在匹配的地方加个标记,记录长度即可,想象原本跳fail的过程,对于自动机上每个fail节点,我们要对他的flag取max,然后加标记,那么我们在建fail边的时候,直接取max就好了,这样就不用跳fail边了。那么我们只要遍历自动机,如果当前文本串的字符不是字母,那么我们直接回到自动机的元节点即可。
结构体数组不要赋初值!默认会把全数组赋值,然后就MLE了。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=7e5+4;
int t,n;
struct node{
int son[26],fail=0,flag;
}trie[maxn];
int p[maxn];
int tot=1;
queue<int>q;
char s[maxn];
void init(){
tot=0;
for(int j=0;j<26;j++){
trie[tot].son[j]=0;
}
trie[tot].fail=0;
trie[tot].flag=0;
}
void insert(char *s){
int len=strlen(s),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!trie[u].son[v]){
trie[u].son[v]=++tot;
for(int j=0;j<26;j++){
trie[tot].son[j]=0;
}
trie[tot].fail=0;
trie[tot].flag=0;
}
u=trie[u].son[v];
}
trie[u].flag=max(trie[u].flag,len);
}
void getfail(){
for(int i=0;i<26;i++)
if(trie[0].son[i])q.push(trie[0].son[i]);
while(!q.empty()){
int u=q.front();q.pop();
trie[u].flag=max(trie[u].flag,trie[trie[u].fail].flag);
for(int i=0;i<26;i++){
int v=trie[u].son[i];
int x=trie[u].fail;
if(!v){
trie[u].son[i]=trie[x].son[i];
}else{
trie[v].fail=trie[x].son[i];
q.push(v);
}
}
}
}
void query(char *s){
int u=0,len=strlen(s);
for(int i=0;i<len;i++)p[i]=0;
for(int i=0;i<len;i++){
int v=-1;
if(s[i]>='A'&&s[i]<='Z')v=s[i]-'A';
if(s[i]>='a'&&s[i]<='z')v=s[i]-'a';
if(v==-1)u=0;
else u=trie[u].son[v];
if(trie[u].flag){
p[i+1]--;
p[i-trie[u].flag+1]++;
}
}
int res=0;
for(int i=0;i<len;i++){
res+=p[i];
if(res==0)putchar(s[i]);
else putchar('*');
}
printf("\n");
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
init();
for(int i=1;i<=n;i++){
scanf("%s",s);
insert(s);
}
getfail();
getchar();
scanf("%[^\n]",s);
query(s);
}
return 0;
}
//D I K L
D - Tea
题意:
懒得写
思路:
分类讨论
看代码应该看得懂
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e6+4;
int t,n;
ll l,r;
ll ans=0;
int main(){
while(cin>>l>>r){
ans=1e18;
ll res=r-l;
if(res<=3)ans=2;
else ans=(res-3+1)/2+2;
if(r<=2)ans=1;
if(r<=1)ans=0;
if(l==0)ans=min(ans,(r+1)/2);
cout<<ans<<endl;
}
return 0;
}
//D I K L
I - Tower Defence
题意:
给你
n
n
n个节点的带边权的树(
n
<
=
1
e
5
n<=1e5
n<=1e5),定义每次删除一个边,对答案的贡献是分开的两颗子树中直径的较大值。求分别删除每条边对答案的贡献和。
思路:
我们分成两种情况来考虑:
如果删除的边不是整棵树的直径上的边,那么对答案的贡献就还是整棵树的直径。
如果删除的边不是整棵树的直径,我们对两边子树的直径取较大值。
我们可以比较简单地证明出,删掉一条边之后,子树的直径一定是由原直径的端点出发的。(如果不是从端点出发的最长链,那么他就会取代现在端点的这条链,加入原直径)。
那么我们就可以预处理出删除直径上的一条边后,子树的最大直径,即对于每个点,求他不经过直径最远能延伸多少,然后左右分别做一次DP,
d
p
[
i
]
=
m
a
x
(
l
e
n
,
n
o
w
+
c
n
t
)
dp[i]=max(len,now+cnt)
dp[i]=max(len,now+cnt),最后再跑一次直径取较大值加到答案中即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,t;
struct node{
ll to,w;
};
vector<node>v[100040];
ll dep[100040];
bool vis[100040];
int root,root2;
ll sum=0;
ll ans=0;
ll res=0;
ll dp1[100040];
ll dp2[100040];
void dfs1(int x,int pre){
if(dep[x]>dep[root])root=x;
for(int i=0;i<v[x].size();i++){
if(v[x][i].to==pre)continue;
dep[v[x][i].to]=dep[x]+v[x][i].w;
dfs1(v[x][i].to,x);
}
}
void dfs2(int x){
vis[x]=1;res++;
for(int i=0;i<v[x].size();i++){
if(dep[x]==dep[v[x][i].to]+v[x][i].w){
dfs2(v[x][i].to);
}
}
}
ll cnt=0;
ll now=0;
ll lenn=0;
void dfs4(int x,int pre,ll d){
cnt=max(cnt,d);
for(int i=0;i<v[x].size();i++){
if(v[x][i].to==pre||vis[v[x][i].to])continue;
dfs4(v[x][i].to,x,d+v[x][i].w);
}
}
int f=0;
void dfs3(int x,int pre){
cnt=0;
if(f==0&&x!=root2){
dfs4(x,0,0);
dp1[x]=max(lenn,now+cnt);
lenn=max(lenn,now+cnt);
}
if(f==1&&x!=root){
dfs4(x,0,0);
dp2[x]=max(lenn,now+cnt);
lenn=max(lenn,now+cnt);
}
for(int i=0;i<v[x].size();i++){
if(v[x][i].to==pre||vis[v[x][i].to]==0)continue;
now+=v[x][i].w;
dfs3(v[x][i].to,x);
}
}
void dfs5(int x,int pre){
if(x!=root2){
ans+=max(dp1[pre],dp2[x]);
}
for(int i=0;i<v[x].size();i++){
if(v[x][i].to==pre||vis[v[x][i].to]==0)continue;
dfs5(v[x][i].to,x);
}
}
int main(){
scanf("%lld",&t);
while(t--){
scanf("%lld",&n);
for(int i=1;i<n;i++){
ll a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
v[a].push_back({b,c});
v[b].push_back({a,c});
}
for(int i=1;i<=n;i++)vis[i]=0,dp1[i]=0,dp2[i]=0;
root=0;
dep[0]=0;
dep[1]=0;
dfs1(1,0);
root2=root;
dep[root]=0;
dfs1(root,0);
sum=dep[root];
ans=0;res=0;
dfs2(root);
ans=1LL*sum*(n-res);
now=0;lenn=0;
f=0;
dfs3(root2,0);
f=1;
now=0;lenn=0;
dfs3(root,0);
dfs5(root2,0);
printf("%lld\n",ans);
for(int i=1;i<=n;i++)v[i].clear();
}
return 0;
}
//K L
K - Barricade
题意:
给你
n
n
n个点
m
m
m条边的图,你需要堵住从1到
n
n
n的所有最短路,堵住每条边会有一个代价。求堵住最短路的最小代价。
思路:
比赛当时,想了一个有的没的DP转移,后来发现不对。
然后其实就是一个网络流裸题,作为网络流选手,没看出来是不应该的,需要自己反思。
就先把最短路扣出来,堵住这些路就是把点1和点
n
n
n,分成两部分,典型的最小割。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int t,n,m;
struct node{
int to,w;
};
vector<node>v[1004];
int dis1[1004],dis2[1004];
queue<int>q;
struct edge{
int to,cap,rev;
};
vector<edge>mp[1004];
int level[1004];//顶点到源点的距离标号
int iter[1004]; //当前弧,在其之前的边已经没有用了
void add(int from,int to,int cap){
mp[from].push_back({to,cap,mp[to].size()});
mp[to].push_back({from,0,mp[from].size()-1});
}
void bfs(int s){
memset(level,-1,sizeof(level));
queue<int>q;
level[s]=0;
q.push(s);
while(!q.empty()){
int v=q.front();q.pop();
for(int i=0;i<mp[v].size();i++){
edge &e=mp[v][i];
if(e.cap>0&&level[e.to]<0){
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
int dfs(int v,int t,int f){
if(v==t)return f;
for(int &i=iter[v];i<mp[v].size();i++){
edge &e=mp[v][i];
if(e.cap>0&&level[v]<level[e.to]){
int d=dfs(e.to,t,min(f,e.cap));
if(d>0){
e.cap-=d;
mp[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
int max_flow(int s,int t){
int flow=0;
while(1){
bfs(s);
if(level[t]<0)return flow;
memset(iter,0,sizeof(iter));
int f;
while((f=dfs(s,t,1e9))>0)flow+=f;
}
return flow;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
if(a==b)continue;
v[a].push_back({b,c});
v[b].push_back({a,c});
}
for(int i=1;i<=n;i++){
dis1[i]=dis2[i]=1e9;
}
dis1[1]=0;
while(!q.empty())q.pop();
q.push(1);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<v[x].size();i++){
if(dis1[v[x][i].to]>dis1[x]+1){
dis1[v[x][i].to]=dis1[x]+1;
q.push(v[x][i].to);
}
}
}
dis2[n]=0;
q.push(n);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<v[x].size();i++){
if(dis2[v[x][i].to]>dis2[x]+1){
dis2[v[x][i].to]=dis2[x]+1;
q.push(v[x][i].to);
}
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<v[i].size();j++){
if(dis1[i]+dis2[v[i][j].to]+1==dis1[n]){
add(i,v[i][j].to,v[i][j].w);
}
}
}
printf("%d\n",max_flow(1,n));
for(int i=1;i<=n;i++)v[i].clear(),mp[i].clear();
}
return 0;
}
//K L
L - Eighty seven
题意:
给你
n
n
n个数(
n
<
=
50
n<=50
n<=50), 每次保证不取小于等于3个数的前提下,问能否取十个数使得这十个数的和为87;
思路:
暴力背包+bitset优化
代码:
#include<bits/stdc++.h>
using namespace std;
bitset<90>s[11];
int a[51],f[51][51][51];
int n;
int solve(int x,int y,int z){
for(int i=0;i<=10;i++)s[i].reset();
s[0][0]=1;
for(int i=1;i<=n;i++){
if(i==x||i==y||i==z||a[i]>87)continue;
for(int j=10;j>0;j--){
s[j]|=s[j-1]<<a[i];
}
}
return s[10][87];
}
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
for(int k=j;k<=n;k++){
f[i][j][k]=solve(i,j,k);
}
}
}
int m;
scanf("%d",&m);
while(m--){
int b[3];
scanf("%d%d%d",&b[0],&b[1],&b[2]);
sort(b,b+3);
if(f[b[0]][b[1]][b[2]])printf("Yes\n");
else printf("No\n");
}
}
}