2020 Multi-University Training Contest 2 解题报告

1001


并查集。考虑按点权大到小把点插入图中,每次插入点到图中,我们不妨设集合\(S\)(其中\(s_i\)为第\(i\)个集合的权值)为与当前点相连的连通块组成的集合,\(w\)为当前节点的权值。易得插入一个点得到的新连通块的公式为:

\[\sum^S (s_i-w)+w \]

那么维护集合我们很自然的想到用并查集维护。最后扫一遍每个点,保证每个连通块都加入答案。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
    ll x=0,f=0;char ch=getchar();
    while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return f? -x:x;
}

#define PII pair <int,int>
#define fr first
#define sc second
#define mp make_pair

const int N=1e5+7;

int n,m;
ll Ans=0;

vector <int> G[N];
PII a[N];
ll vis[N],ans[N];

int fa[N],rk[N],vs[N];
int find(int x){ return fa[x]==x? x:(fa[x]=find(fa[x]));}
void merge(int x,int y){
    x=find(x);y=find(y);
    x==y? 0:rk[x]>=rk[y]? fa[y]=x,rk[x]+=rk[y]:fa[x]=y,rk[y]+=rk[x];
}

int main(){
    int T=input();
    while(T--){
        n=input(),m=input();

        int sta=1;Ans=0;
        for(int i=1;i<=n;i++){
            int val=input();
            a[i]=mp(val,i);
            fa[i]=i,rk[i]=1,G[i].clear();
            vis[i]=0,ans[i]=0;vs[i]=0;
        }

        for(int i=1;i<=m;i++){
            int u=input(),v=input();
            G[u].push_back(v),G[v].push_back(u);
        }

        sort(a+1,a+1+n);

        for(int i=n;i>=1;i--){
            int u=a[i].sc,val=a[i].fr;
            int cnt=0;
            for(auto v:G[u]){
                if(vis[v]){
                    if(find(u)!=find(v)) ans[u]+=(ans[find(v)]-val);
                    merge(u,v);
                }
            }
            vis[u]=1;
            ans[find(u)]=ans[u]+val;
        }

        ll res=0;
        for(int i=1;i<=n;++i){
            if(vs[find(i)]==0) res+=ans[find(i)],vs[find(i)]=1;
        }
        printf("%lld\n",res);
    }
}

1012

比赛的时候dp没推出来,就是菜☹。我们观察到B串非常短\(m\)最大只有20,我们考虑把复杂度尽量与询问的区间长度无关,与B串的长度有关。我们这个时候想到了一个非常好用的结构序列自动机,序列自动机能够让我们在原串上快速移动,这样我们的所有操作就与区间长度无关,而与\(m\)有关了。

现在问题转换为如何通过序列自动机,求区间的LCS,为啥要求LCS,因为我编辑距离只有增加和删除,那么最优一定是尽可能的保留更多的字符不被删除,那么我们就很自然的想到要求LCS,最后答案一定是在原串中要删掉多少个字符\((r-l+1-LCS)\)加上要加入多少个字符使得答案匹配的数量\(|B|-LCS\),即\((r-l+1-LCS)+|B|-LCS\)。我们不妨记区间\([l,r]\)的LCS为\(LCS(l,r)\),那么我们求\(LCS(l,r)\),需要考虑\(dp[i][j]\)表示开始\(B\)的前\(i\)个位置匹配长度为\(j\)的LCS最远能到哪里。那么转移方程易得:

\[dp[i][j]=min(dp[i][j],nxt[dp[i-1][j-1]][s[i]-'a']) \]

最后扫一遍\(dp[m][i]\)求答案即可。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

const int N=1e5+7;

int nxt[N][27];

char a[N],b[N];
int lena,lenb;

void init(){
	memset(nxt,0,sizeof nxt);
	int len=strlen(a+1);
	for(int i=0;i<26;i++) nxt[len][i]=1e9;
	for(int i=len;i>=1;i--){
		for(int j=0;j<=25;j++) nxt[i-1][j]=nxt[i][j];
		nxt[i-1][a[i]-'a']=i;
	}
}

int dp[27][27];

ll getans(int l,int r){
	memset(dp,0x3f,sizeof dp);

	dp[0][0]=l-1;
	for(int i=1;i<=lenb;i++){
		dp[i][0]=l-1;
		for(int j=1;j<=i;j++){
			dp[i][j]=dp[i-1][j];
			if(dp[i-1][j-1]<=r){
				dp[i][j]=min(dp[i][j],nxt[dp[i-1][j-1]][b[i]-'a']);
			}
		}
	}
	
	int res=1e9;
	for(int i=0;i<=lenb;i++){
		if(dp[lenb][i]<=r) res=min(res,(r-l+1-i)+(lenb-i));
	}
	return res;
}


int main(){
	int T=input();

	while(T--){
		scanf("%s%s",a+1,b+1);
		init();
		lena=strlen(a+1),lenb=strlen(b+1);

		int Q=input();
		for(int i=1;i<=Q;i++){
			int l=input(),r=input();
			printf("%lld\n",getans(l,r));
		}
	}
}

猜你喜欢

转载自www.cnblogs.com/-aether/p/13369518.html