对于每一条二次函数,选择前 n n n个最小的值对应的横坐标。该二次函数对应的 i i i,连向这n个点。再建立超级原点和超级终点。对于每个 k k k( 1 < = k < = n 1<= k <= n 1<=k<=n),跑一次增广路,答案就是前缀和。
#include
using namespace std;
typedef long long LL;
const int MAXN = 55;
struct Edge
{
int from, to;
LL cap, flow, cost;
Edge(int u, int v, LL c, LL f, LL cc)
: from(u), to(v), cap(c), flow(f), cost(cc) {}
};
const int maxn = 10001;
const int MAXM = 100001;
const LL INF = 6e18;
vector<LL> res;
struct MCMF
{
int n, m;
vector<Edge> edges;
vector<int> G[maxn];
int inq[maxn]; //是否在队列中
LL d[maxn]; //bellmanford
int p[maxn]; //上一条弧
LL a[maxn]; //可改进量
void init(int n)
{
this->n = n;
for (int i = 0; i <= n; ++i)
G[i].clear();
edges.clear();
}
void addEdge(int from, int to, LL cap, LL cost)
{
edges.emplace_back(Edge(from, to, cap, 0, cost));
edges.emplace_back(Edge(to, from, 0, 0, -cost));
m = int(edges.size());
G[from].emplace_back(m - 2);
G[to].emplace_back(m - 1);
}
bool spfa(int s, int t, LL &flow, LL &cost)
{
for (int i = 1; i <= n; ++i)
d[i] = INF;
memset(inq, 0, sizeof(inq));
d[s] = 0;
inq[s] = 1;
p[s] = 0;
queue<int> q;
a[s] = INF;
q.push(s);
while (!q.empty())
{
int u = q.front();
q.pop();
inq[u] = 0;
for (int i = 0; i < int(G[u].size()); ++i)
{
Edge &e = edges[G[u][i]];
if (e.cap > e.flow && d[e.to] > d[u] + e.cost)
{
d[e.to] = d[u] + e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u], e.cap - e.flow);
if (!inq[e.to])
{
q.push(e.to);
inq[e.to] = 1;
}
}
}
}
if (d[t] == INF)
return false;
flow += a[t];
cost += (LL)d[t] * (LL)a[t];
res.push_back(d[t]);
for (int u = t; u != s; u = edges[p[u]].from)
{
edges[p[u]].flow += a[t];
edges[p[u] ^ 1].flow -= a[t];
}
return true;
}
// int testnum = 1;
LL MincostMaxflow(int s, int t, LL &cost)
{
LL flow = 0;
cost = 0;
// cout << "hello1\n";
while (spfa(s, t, flow, cost))
{
// cout << "hello2\n";
// cout << "testnum = " << testnum++ << '\n';
}
return flow;
}
} mcmf;
LL n, m;
LL a[MAXN], b[MAXN], c[MAXN];
set<LL> allpoint;
map<LL, int> mpid;
LL nodeval[MAXN * MAXN];
struct Node
{
LL p, val;
bool operator<(const Node a) const
{
return val > a.val;
}
};
int ju(LL A, LL B)
{
if (B < (-2) * A * m)
{
return 3;
}
else if (B > (-2) * A)
{
return 1;
}
else
{
return 2;
}
}
LL cal(LL A, LL B, LL j) { return A * j * j + B * j; }
vector<LL> getNode(LL A, LL B)
{
vector<Node> vc;
LL Z = -(B / (2 * A));
int judge = ju(A, B);
if (judge == 1)
{
for (LL i = 1; i <= n; i++)
{
vc.push_back({i, cal(A, B, i)});
// cout << "i = " << i << '\n';
}
}
else if (judge == 3)
{
for (LL i = m; i >= m - n + 1; i--)
{
vc.push_back({i, cal(A, B, i)});
}
}
else
{
for (LL i = Z; i >= Z - n + 1 && i >= 1; i--)
vc.push_back({i, cal(A, B, i)});
for (LL i = Z + 1; i <= Z + n && i <= m; i++)
vc.push_back({i, cal(A, B, i)});
}
sort(vc.begin(), vc.end(), [&](Node a, Node b) {
return a.val < b.val;
});
vector<LL> ans;
for (int i = 0; i < n; i++)
{
ans.push_back(vc[i].p);
allpoint.insert(vc[i].p);
}
return ans;
}
vector<LL> ans[MAXN];
int main()
{
// freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取
// freopen("out.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中
// cout << 1 << '\n';
// return 0;
int T;
scanf("%d", &T);
while (T--)
{
// int testnum = 1;
// cout << "testnum = " << testnum++ << '\n';
res.clear();
allpoint.clear();
scanf("%lld %lld", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%lld %lld %lld", &a[i], &b[i], &c[i]);
}
// int Nodenum = n + int(allpoint.size()) + 10;
// cout << "testnum = " << testnum++ << '\n';
// cout << "testnum = " << testnum++ << '\n';
for (int i = 1; i <= n; i++)
{
ans[i] = getNode(a[i], b[i]);
}
int cntnode = n;
for (auto it : allpoint)
{
mpid[it] = ++cntnode;
nodeval[cntnode] = it;
}
mcmf.init(cntnode + 3);
for (int i = 1; i <= n; i++)
{
for (auto v : ans[i])
{
// cout << i << " " << mpid[v] << " " << cal(a[i], b[i], v) + c[i] << '\n';
mcmf.addEdge(i, mpid[v], 1, cal(a[i], b[i], v) + c[i]);
}
}
int S = cntnode + 1, T = cntnode + 2;
for (int i = 1; i <= n; i++)
{
// cout << S << " " << i << '\n';
mcmf.addEdge(S, i, 1, 0);
}
for (auto v : allpoint)
{
// cout << mpid[v] << " " << T << '\n';
mcmf.addEdge(mpid[v], T, 1, 0);
}
// cout << "testnum = " << testnum++ << '\n';
LL cost = 0;
LL flow = mcmf.MincostMaxflow(S, T, cost);
// cout << "flow = " << flow << '\n';
// cout << "testnum = " << testnum++ << '\n';
LL pre = 0;
int lenres = res.size();
for (int i = 0; i < lenres; i++)
{
pre += res[i];
printf("%lld%c", pre, (i == lenres - 1 ? '\n' : ' '));
}
}
// cin >> T;
}
二分枚举可能的直径 m i d mid mid,每一次通过树形dp判断是否可能存在满足条件的选取方案。
令 f [ i ] [ j ] f[i][j] f[i][j]为以第 i i i个节点的子树有 j j j条边选取了 a a a边另外的边选取了 b b b边的离 i i i点最远的边的最小值,且要符合子树的直径符合条件。
#include
using namespace std;
typedef long long LL;
const int MAXN = 2e4 + 5;
int N, K;
struct Node
{
int to;
LL a, b;
Node(int _to, LL _a, LL _b)
{
to = _to;
a = _a;
b = _b;
}
};
LL f[MAXN][22];
LL arrtmp[22];
int sz[MAXN];
vector<Node> edg[MAXN];
void dfs(int u, int fa, LL limi)
{
sz[u] = 0;
for (int i = 0; i <= K; i++)
f[u][i] = 0;
for (auto v : edg[u])
{
if (v.to == fa)
continue;
dfs(v.to, u, limi);
int num = min(sz[u] + sz[v.to] + 1, K);
for (int i = 0; i <= K; i++)
arrtmp[i] = limi + 1;
for (int j = 0; j <= sz[u]; j++)
for (int k = 0; k + j <= K && k <= sz[v.to]; k++)
{
if (f[u][j] + f[v.to][k] + v.a <= limi)
arrtmp[j + k + 1] = min(arrtmp[j + k + 1], max(f[u][j], f[v.to][k] + v.a));
if (f[u][j] + f[v.to][k] + v.b <= limi)
arrtmp[j + k] = min(arrtmp[j + k], max(f[u][j], f[v.to][k] + v.b));
}
for (int i = 0; i <= K; i++)
f[u][i] = arrtmp[i];
sz[u] = num;
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &N, &K);
for (int i = 1; i <= N; i++)
edg[i].clear();
int u, v;
LL a, b;
LL L = 0, R = 0;
for (int i = 1; i <= N - 1; i++)
{
scanf("%d %d %lld %lld", &u, &v, &a, &b);
edg[u].push_back(Node(v, a, b));
edg[v].push_back(Node(u, a, b));
R += max(a, b);
}
LL ans = R;
while (L <= R)
{
LL mid = (L + R) >> 1;
dfs(1, 0, mid);
// cout << "mid = " << mid << '\n';
if (f[1][K] <= mid)
{
R = mid - 1;
ans = mid;
}
else
{
L = mid + 1;
}
}
printf("%lld\n", ans);
}
// cin >> T;
}
Shortest judge solution: 879 Bytes.
考虑修改完毕后的A 串和B 串,它们对应位置的字符都相等,对于每一位:
• 如果两个串在这一位都做了插入操作,那么可以同时不做插入操作使得操作 次数减少2。
• 如果一个串A 在这一位做了插入操作,另一个串B 这一位不动,那么可以通过A 这一位不动,B 删除这一位达到同样的效果。
因此可以发现插入操作是没用的,所以两个串A 和B 的距离等于 ∣ A ∣ + ∣ B ∣ − 2 ∗ L C S ( A , B ) |A| + |B| - 2 * LCS(A, B) ∣A∣+∣B∣−2∗LCS(A,B),其中LCS 表示最长公共子序列。预处理出 g [ i ] [ j ] g[i][j] g[i][j] 表示 A [ i . . . n ] A[i...n] A[i...n] 里字符 j j j最早出现的下标。对于每个询问通过DP 求出LCS,设 f [ i ] [ j ] f[i][j] f[i][j] 表示与 B [ 1... i ] B[1...i] B[1...i] 的公共序列长度达到 j j j的 A [ l . . . r ] A[l...r] A[l...r]的最短
前缀的长度,利用 g g g数组进行转移。
#include
using namespace std;
const int MAXN = 100005;
const int MAXM = 30;
char a[MAXN], b[MAXM];
int A[MAXN], B[MAXM];
int g[MAXN][MAXM];
int lena, lenb;
inline void up(int &a, int b) { a > b ? (a = b) : 0; }
void getp()
{
for (int i = 0; i < 26; i++)
{
g[lena + 1][i] = lena + 1;
}
for (int i = lena; i > 0; i--)
{
for (int j = 0; j < 26; j++)
{
g[i][j] = g[i + 1][j];
}
g[i][A[i]] = i;
}
}
int f[MAXM][MAXM];
int cal(int l, int r)
{
for (int i = 0; i <= lenb; i++)
{
for (int j = 0; j <= i; j++)
{
f[i][j] = MAXN;
}
}
f[0][0] = l - 1;
for (int i = 0; i < lenb; i++)
{
for (int j = 0; j <= i; j++)
{
up(f[i + 1][j], f[i][j]);
if (f[i][j] < r)
{
up(f[i + 1][j + 1], g[f[i][j] + 1][B[i + 1]]);
}
}
}
for (int j = lenb; j >= 0; j--)
{
if (f[lenb][j] <= r)
return j;
}
return 0;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%s %s", a + 1, b + 1);
lena = strlen(a + 1), lenb = strlen(b + 1);
for (int i = 1; i <= lena; i++)
{
A[i] = a[i] - 'a';
}
for (int i = 1; i <= lenb; i++)
{
B[i] = b[i] - 'a';
}
getp();
int q;
scanf("%d", &q);
while (q--)
{
int l, r;
scanf("%d %d", &l, &r);
printf("%d\n", r - l + 1 + lenb - 2 * cal(l, r));
}
}
}