A

二分答案 $x$,加入所有 $\leq x$ 的边后判断图是否强连通即可。

一种方法是判联通性+tarjan,但是可以建正向图和反向图分别从 $1$ 开始 dfs 。(这样说明 $1$ 和任何点都有两条路径,所以任意两点都有两条路径)

有向图 tarjan 不要判 father,,一开始因为这个吃了罚时。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 2e5 + 5;

std::vector<int> G[MAXN];

struct Edge{
    int u,v,w;

    inline bool operator < (const Edge &t) const {
        return w < t.w;
    }
}e[MAXN];

int n,m;
int dfn[MAXN],low[MAXN],ts;
bool ins[MAXN];
int stk[MAXN],tp;
int t = 0;

inline void dfs(int v){
    dfn[v] = low[v] = ++ts;ins[v] = 1;stk[++tp] = v;
    for(auto x:G[v]){
        if(!dfn[x]){
            dfs(x);
            low[v] = std::min(low[v],low[x]);
        }
        else if(ins[x]) low[v] = std::min(low[v],dfn[x]);
    }
    if(low[v] == dfn[v]){
        ++t;int y = -1;
        do{
            y = stk[tp--];
            ins[y] = 0;
        }while(y != v);
    }
}

inline bool chk(int k){
    FOR(i,1,n) G[i].clear(),ins[i] = dfn[i] = low[i] = 0;
    FOR(i,1,k) G[e[i].u].pb(e[i].v);
    ts = t = 0;dfs(1);bool flag = 1;
    FOR(i,1,n) flag &= (dfn[i] != 0);
    return t == 1 && flag;
}

int main(){
//    freopen("A.in","r",stdin);
    scanf("%d%d",&n,&m);
    FOR(i,1,m) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    std::sort(e+1,e+m+1);
    int l = 1,r = m,ans = -1;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(chk(mid)) ans = mid,r = mid-1;
        else l = mid+1;
    }
    printf("%d\n",ans==-1?ans:e[ans].w);
    return 0;
}

B

设询问是 $u \to lca \to v$,把答案分成三部分:$u \to lca$ 的点之间的贡献, $v \to lca$ 之间的贡献,$u \to lca$ 和 $lca \to v$ 的贡献。

$u \to lca$ 和 $lca \to v$ 之间的贡献求个链上和就行了。

$u \to lca$ 的贡献可以先处理处到根的贡献,然后减去多余的(多余的分两类,$lca$ 到根内部的贡献和 $lca$ 到根与 $lca$ 到 $u$ 之间的贡献)

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 1e5 + 5;
const int MAXM = 16;

struct Edge{
    int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt;

inline void add(int u,int v){
    e[++cnt] = (Edge){v,head[u]};head[u] = cnt;
}

int n,m;
int f[MAXN][MAXM+1],dep[MAXN];
int a[MAXN],b[MAXN];
LL sma[MAXN],smb[MAXN];
LL ra[MAXN],rb[MAXN];

inline void dfs(int v,int fa=0){
    f[v][0] = fa;dep[v] = dep[fa]+1;
    FOR(i,1,MAXM) f[v][i] = f[f[v][i-1]][i-1];
    sma[v] = sma[fa]+a[v];
    smb[v] = smb[fa]+b[v];
    ra[v] = ra[fa]+a[v]*smb[fa];
    rb[v] = rb[fa]+b[v]*sma[fa];
    for(int i = head[v];i;i = e[i].nxt){
        if(e[i].to == fa) continue;
        dfs(e[i].to,v);
    }
}

inline int lca(int x,int y){
    if(dep[x] != dep[y]){
        if(dep[x] < dep[y]) std::swap(x,y);
        int d = dep[x]-dep[y];
        FOR(i,0,MAXM){
            if((d>>i)&1) x = f[x][i];
        }
    }
    if(x == y) return x;
    ROF(i,MAXM,0){
        if(f[x][i] == f[y][i]) continue;
        x = f[x][i],y = f[y][i];
    }
    return f[x][0];
}

int main(){
    scanf("%d%d",&n,&m);
    FOR(i,2,n){
        int f;scanf("%d",&f);add(f,i);
    }
    FOR(i,1,n) scanf("%d",a+i);
    FOR(i,1,n) scanf("%d",b+i);
    dfs(1);
    FOR(i,1,m){
        int x,y;scanf("%d%d",&x,&y);
        int l = lca(x,y);
        LL ans = (sma[x]-sma[l])*(smb[y]-smb[l]);
        ans += ra[x]+rb[y]-ra[f[l][0]]-rb[f[l][0]];
        ans -= (sma[x]-sma[f[l][0]])*smb[f[l][0]];
//        DEBUG(ans);
        ans -= (smb[y]-smb[f[l][0]])*sma[f[l][0]];
        printf("%lld\n",ans);
    }
    return 0;
}

C

设 $f_{i,j}$ 表示前 $i$ 个数,第 $i$ 个数填了颜色 $j$ 的答案,为了方便转移设 $g_{i,j}$ 表示考虑前 $i$ 个数,第 $i$ 个数不填颜色 $j$ 的答案,转移就有 $f_{i,j} = g_{i-1,j}$,$g_i$ 可以处理前缀后缀 min 方便求出。

求答案我们考虑去枚举相同颜色的区间和颜色,那么我们要对前缀后缀都求出一个 $g$,设为 $g_1,g_2$。

设 $sm_{i,j}$ 表示颜色 $i$ 的前 $j$ 的和,枚举区间 $l,r$ 涂成一种颜色 $c$,答案就是 $sm_{c,r}-sm_{c,l-1}+g_{l-1,c}+g_{r+1,c}$,直接枚举是 $O(n^3)$ 的,但是我们可以先枚举颜色和左端点,左端点从右到左枚举,变成了一个滑动窗口问题,单调队列维护即可。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

inline char nc(){
    #define SIZE 1000000+3
    static char buf[SIZE],*p1 = buf+SIZE,*p2 = buf+SIZE;
    if(p1 == p2){
        p1 = buf;p2 = buf+fread(buf,1,SIZE,stdin);
        if(p1 == p2) return -1;
    }
    return *p1++;
    #undef SIZE
}

template <typename T>
inline void read(T &x){
    x = 0;int flag = 0;char ch = nc();
    while(!isdigit(ch)){
        if(ch == '-') flag = 1;
        ch = nc();
    }
    while(isdigit(ch)){
        x = (x<<1) + (x<<3) + (ch^'0');
        ch = nc();
    }
    if(flag) x = -x;
}

const int MAXN = 2000+5;
int g1[MAXN][MAXN],g2[MAXN][MAXN],f[MAXN][MAXN];
int n,m,k;
int a[MAXN][MAXN];
int pre[MAXN],suf[MAXN];
int sm[MAXN][MAXN];

inline void init(int f[],int g[]){
    pre[0] = suf[m+1] = 1e9;
    FOR(i,1,m) pre[i] = std::min(pre[i-1],f[i]);
    ROF(i,m,1) suf[i] = std::min(suf[i+1],f[i]);
    FOR(i,1,m) g[i] = std::min(pre[i-1],suf[i+1]);
}

int h[MAXN];

int main(){
    read(n);read(m);read(k);
    FOR(i,1,n) FOR(j,1,m) read(a[i][j]);
    FOR(i,1,m) FOR(j,1,n) sm[i][j] = sm[i][j-1]+a[j][i];
    FOR(i,1,m) f[1][i] = a[1][i];
    init(f[1],g1[1]);
    FOR(i,2,n){
        FOR(j,1,m){
            f[i][j] = g1[i-1][j]+a[i][j];
        }
        init(f[i],g1[i]);
    }
    FOR(i,1,m) f[n][i] = a[n][i];
    init(f[n],g2[n]);
    ROF(i,n-1,1){
        FOR(j,1,m){
            f[i][j] = g2[i+1][j]+a[i][j];
        }
        init(f[i],g2[i]);
    }
    // 选择区间[l,r]涂成 颜色 v
    // sm[v][r]+g2[r+1][v] +g1[l-1][v]-sm[v][l-1]
    // h1[i] = min_v g1[i][v]-sm[v][i]
    // h2[i] = min_v sm[v][i]+g2[i+1][v]
    // h1[i-1]+h2[i]
    int ans = 1e9;
    FOR(v,1,m){
        //h2[n+1] = 1e9;
        std::deque<int> q;
        FOR(i,1,n) h[i] = g2[i+1][v]+sm[v][i];
        ROF(l,n,1){
            // sm[v][r]+g2[r+1][v]+g1[l-1][v]-sm[v][l-1]
            while(!q.empty() && q[0]-l+1 > k) q.pop_front();
            while(!q.empty() && h[q.back()] >= h[l]) q.pop_back();
            q.pb(l);
            ans = std::min(ans,h[q.front()]+g1[l-1][v]-sm[v][l-1]);
        }
    }
    printf("%d\n",ans);
    return 0;
}

D

一开始傻了。。这个题不应该直接在 AC 自动机上跑,可以搞下来跑。

建出 AC 自动机,相当于边有边权,要求经过某些点的最短路径。可以拆点+bfs 求出每个好点到所有点的最短路(钦定不能走坏点),状压 dp 即可:设 $f_{v,S}$ 表示走完了 $S$ 内的点,当前在 $v$ 的答案,转移枚举下一个怎么走即可。

可能是状态不满。。?反正过了。考试的时候也要写啊。。

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 1e5 + 5;
int ch[MAXN][4],tot=1,fail[MAXN],rt = 1;
int n,m,k;
bool bd[MAXN];
int G[MAXN];

inline void insert(char str[],int opt,int id=0){
    int v = rt;
    FOR(i,1,k){
        int o = str[i]-'1';
        if(!ch[v][o]) ch[v][o] = ++tot;
        v = ch[v][o];
    }
    if(opt == 1) G[id] = v;
    else bd[v] = 1;
}

struct Edge{
    int to,w,nxt;
}e[MAXN<<2];
int head[MAXN],cnt;

inline void add(int u,int v,int w){
    if(bd[u] || bd[v]) return;
//    printf("%d %d %d\n",u,v,w);
    e[++cnt] = (Edge){v,w,head[u]};head[u] = cnt;
}

inline void build(){
    std::queue<int> q;
    FOR(i,0,3){
        if(ch[rt][i]) fail[ch[rt][i]] = rt,q.push(ch[rt][i]);
        else ch[rt][i] = rt;
    }
    while(!q.empty()){
        int v = q.front();q.pop();
        FOR(i,0,3){
            if(ch[v][i]) fail[ch[v][i]] = ch[fail[v]][i],q.push(ch[v][i]);
            else ch[v][i] = ch[fail[v]][i];
        }
    }
    FOR(i,1,tot) FOR(j,0,3) add(i,ch[i][j],j+1);
}

char str[123];
int dis[21][MAXN];
bool used[MAXN];

inline void dij(int dis[],int s){
    std::priority_queue<P,std::vector<P>,std::greater<P> > q;
    FOR(i,1,tot) dis[i] = 1e9,used[i] = 0;
    q.push(MP(dis[s]=0,s));
    while(!q.empty()){
        int v = q.top().se;q.pop();
        if(used[v]) continue;
        used[v] = 1;
        for(int i = head[v];i;i = e[i].nxt){
            if(bd[e[i].to]) continue;
            if(dis[e[i].to] > dis[v] + e[i].w){
                dis[e[i].to] = dis[v]+e[i].w;
                q.push(MP(dis[e[i].to],e[i].to));
            }
        }
    }
}

int f[(1<<20)+5][20];

int main(){
//    int sz = sizeof(f)+sizeof(ch)+sizeof(fail)+sizeof(bd)+sizeof(G)+sizeof(e)+sizeof(head)+sizeof(used)+sizeof(dis);
//    DEBUG(sz/1024/1024);
//    freopen("D.in","r",stdin);
//    freopen("D.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    FOR(i,1,n) scanf("%s",str+1),insert(str,1,i);
    FOR(i,1,m) scanf("%s",str+1),insert(str,2);
    build();
    dij(dis[0],1);
    FOR(i,1,n) dij(dis[i],G[i]);
    CLR(f,0x3f);
    FOR(i,0,n-1) f[(1<<i)][i] = dis[0][G[i+1]];
    FOR(S,1,(1<<n)-1){
        FOR(i,0,n-1){
            if(f[S][i] == 0x3f3f3f3f) continue;
            if(!((S>>i)&1)) continue;
            FOR(j,0,n-1){
                if((S>>j)&1) continue;
                f[S^(1<<j)][j] = std::min(f[S^(1<<j)][j],f[S][i]+dis[i+1][G[j+1]]);
            }
        }
    }
    int ans = 1e9;
    FOR(i,0,n-1) ans = std::min(ans,f[(1<<n)-1][i]);
    printf("%d\n",ans);
    return 0;
}
Last modification:October 5th, 2020 at 08:55 pm
如果觉得我的文章对你有用,请随意赞赏