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;
}