DP&图论 DAY 6 下午 考试




3 5 10 3 1 3 437 1 2 282 1 5 328 1 2 519 1 2 990 2 3 837 2 4 267 2 3 502 3 5 613 4 5 132 1 3 4 10 13 4 1 6 484 1 3 342 2 3 695 2 3 791 2 8 974 3 9 526 4 9 584 4 7 550 5 9 914 6 7 444 6 8 779 6 10 350 8 8 394 9 10 3 7 10 9 4 1 2 330 1 3 374 1 6 194 2 4 395 2 5 970 2 10 117 3 8 209 4 9 253 5 7 864 8 5 10 6


437 526 641

题解
>50 pt dij 跑暴力
(Floyd太慢了QWQ O(n^3))
枚举每个点作为起点,dijkstra,跑暴力 O( (n+m)logn ),寻找全局最短路
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
inline int read()
{
int ans=0;
char last=' ',ch=getchar();
while(ch<'0'||ch>'9') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
typedef pair<int,int> pa;
const int maxn=1e5+10;
const int maxm=2e5+10;
int T;
int n,m,k;
struct node
{
int to,nxt,dis;
}edge[maxm];
int head[maxn],cnt=0;
void addedge(int u,int v,int w)
{
edge[++cnt].to =v;edge[cnt].dis =w;edge[cnt].nxt =head[u];head[u]=cnt;
edge[++cnt].to =u;edge[cnt].dis =w;edge[cnt].nxt =head[v];head[v]=cnt;
}
int dis[maxn];
int a[maxn];
void dijkstra(int s)
{
priority_queue<pa,vector<pa>,greater<pa> >q;
q.push(make_pair(s,dis[s]=0));
while(!q.empty() )
{
pa now=q.top();
q.pop() ;
if(now.second !=dis[now.first ]) continue;
for(int i=head[now.first ],v;i;i=edge[i].nxt )
{
if(v=edge[i].to ,dis[v]>dis[now.first ]+edge[i].dis )
q.push(make_pair(v,dis[v]=dis[now.first]+edge[i].dis));
}
}
}
int main()
{
T=read();
for(int t=1;t<=T;t++)
{
cnt=0;
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
memset(a,0,sizeof(a));
n=read();m=read();k=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
addedge(u,v,w);
}
for(int i=1;i<=k;i++) a[i]=read();
int ans=0x3f3f3f3f;
for(int i=1;i<=k;i++)
{
memset(dis,0x3f,sizeof(dis));
dijkstra(a[i]);
for(int j=1;j<=k;j++)
{
if(a[i]==a[j]) continue;
ans=min(ans,dis[a[j]]);
}
}
if(ans>=0x3f3f3f3f) ans=-1;
printf("%d\n",ans);
}
return 0;
}
>100pt 考虑优化枚举量
因为答案是两个编号不同的点,所以对应的二进制编码至少有一位不同
枚举二进制的每一位
假设枚举到第 i 位,把这一位是 1 的点设置为源点,是 0 的设置为汇点,跑一遍多源多汇最短路
设置一个超级源点,向所有第一层集合的点连一条长度为 0 的边
设置一个超级汇点,所有最后一个集合的点向超级汇点连一条长度为 0 的边

跑从超级源点到超级汇点的最短路
跑最多32次就可以得到答案
这两个集合既可以是 1~n ,也可以是 1~k
显然 1~k 更优
#include <queue>
#include <cstdio>
#include <cstring>
template <class cls>
inline cls min(const cls & a, const cls & b) {
return a < b ? a : b;
}
const int mxn = 100005;
const int mxm = 500005;
const int inf = 0x3f3f3f3f;
int n, m, k;
int points[mxn];
int tot;
int hd[mxn];
int nt[mxm];
int to[mxm];
int vl[mxm];
inline void add_edge(int u, int v, int w) {
nt[++tot] = hd[u];
to[tot] = v;
vl[tot] = w;
hd[u] = tot;
}
int dis[mxn];
struct data {
int u, d;
data(int _u, int _d) :
u(_u), d(_d) {}
bool operator < (const data & that) const {
return d > that.d;
}
};
std::priority_queue<data> heap;
int main() {
int cas;
scanf("%d", &cas);
for (int c = 0; c < cas; ++c) {
scanf("%d%d%d", &n, &m, &k);
memset(hd, 0, sizeof(int) * (n + 5)); tot = 0;
for (int i = 0, u, v, w; i < m; ++i) {
scanf("%d%d%d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w);
}
for (int i = 0; i < k; ++i)
scanf("%d", points + i);
int ans = inf;
for (int i = 1; i < k; i <<= 1) {
memset(dis, inf, sizeof(int) * (n + 5));
for (int j = 0, p; j < k; ++j)
if (p = points[j], (j & i) == 0)
heap.push(data(p, dis[p] = 0));
while (!heap.empty()) {
int u = heap.top().u;
int d = heap.top().d;
heap.pop();
if (dis[u] != d)
continue;
for (int e = hd[u], v, w; e; e = nt[e])
if (v = to[e], w = vl[e], dis[v] > d + w)
heap.push(data(v, dis[v] = d + w));
}
for (int j = 0, p; j < k; ++j)
if (p = points[j], (j & i) != 0)
ans = min(ans, dis[p]);
}
printf("%d\n", ans == inf ? -1 : ans);
}
return 0;
}




3 5 10 5 4 10 8 1 10 1 3 1 4 1 5 1 3 2 1 2 5 4 3 4 3 4 5 5 1 1 4 4 6 1 9 4 7 2 9 5 10 5 2 8 8 10 10 2 1 2 3 3 2 3 4 3 1 3 2 3 4 4 1 5 4 5 1 1 4 2 3 4 7 3 10 1 5 5 10 5 9 9 8 2 1 1 5 1 5 2 1 2 4 2 4 2 4 3 2 3 1 4 3 4 3 5 9 3 9 2 7 5 1 5 4


40 60 90 70 90 8 30 70 100 10 9 81 63 14

题解
50pt dfs 暴力
观察题目发现我们只需要找到对于一个点的
就好
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
inline ll read()
{
ll ans=0;
char last=' ',ch=getchar();
while(ch<'0'||ch>'9') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const ll maxn=2e5+10;
const ll maxm=4e5+10;
ll T;
ll n,m,k;
ll w[maxn],son[maxn];
ll head[maxn],to[maxm],nxt[maxn];
bool vis[maxn];
void addedge(ll u,ll v,ll cnt)
{
to[cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void chuli(ll u)
{
if(head[u]==0) return;
if(vis[u]) return;
vis[u]=1;
for(ll i=head[u];i;i=nxt[i])
{
ll v=to[i];
chuli(v);
if(w[son[v]]>w[son[u]]) son[u]=son[v];
}
return;
}
int main()
{
// freopen("neural.in","r",stdin);
// freopen("ceshi.txt","w",stdout);
T=read();
for(ll t=1;t<=T;t++)
{
memset(w,0,sizeof(w));
memset(son,0,sizeof(son));
memset(head,0,sizeof(head));
memset(nxt,0,sizeof(nxt));
memset(to,0,sizeof(to));
memset(vis,0,sizeof(vis));
n=read();m=read();k=read();
for(ll i=1;i<=n;i++)
w[i]=read(),son[i]=i;
for(ll i=1;i<=m;i++)
{
ll u,v;
u=read();v=read();
addedge(u,v,i);
}
for(ll i=1;i<=n;i++) chuli(i);
for(ll i=1;i<=k;i++)
{
ll u,x;
u=read();x=read();
printf("%lld\n",(long long)x*w[son[u]]);
}
}
return 0;
}
>100pt
建反向边,tarjan然后拓扑就行了
( 然后我们发现一个大佬ych的思路
思路是tarjan缩点,一个强连通分量的初始ans就是这个强连通分量里面点的最大值。然后建立新图,找到入度为0的点开始dfs,然后更新强连通分量的ans。
询问点就是找点所在的强连通分量,输出强连通分量的ans就ok )
#include <cstdio>
#include <cstring>
template <class cls>
inline cls min(const cls & a, const cls & b) {
return a < b ? a : b;
}
template <class cls>
inline cls max(const cls & a, const cls & b) {
return a > b ? a : b;
}
const int mxn = 200005;
const int mxm = 400005;
int n, m, k, w[mxn];
struct edge {
int u, v;
} edges[mxm];
int tot;
int hd[mxn];
int to[mxm << 1];
int nt[mxm << 1];
inline void add_edge(int u, int v) {
nt[++tot] = hd[u];
to[tot] = v;
hd[u] = tot;
}
int tim;
int cnt;
int top;
int dfn[mxn];
int low[mxn];
int stk[mxn];
int scc[mxn];
void tarjan(int u) {
dfn[u] = low[u] = ++tim; stk[++top] = u;
for (int e = hd[u], v; e; e = nt[e])
if (v = to[e], scc[v] == 0) {
if (dfn[v] == 0)tarjan(v),
low[u] = min(low[u], low[v]);
else
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
cnt += 1;
do {
scc[stk[top]] = cnt;
} while (stk[top--] != u);
}
}
int oe[mxn];
int mx[mxn];
int que[mxn];
void bfs() {
int l = 0, r = 0;
for (int i = 1; i <= cnt; ++i)
if (oe[i] == 0)
que[r++] = i;
while (l < r) {
int u = que[l++];
for (int e = hd[u], v; e; e = nt[e])
if (v = to[e], mx[v] = max(mx[v], mx[u]), --oe[v] == 0)
que[r++] = v;
}
}
int main() {
int cas;
scanf("%d", &cas);
for (int c = 0; c < cas; ++c) {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", w + i);
memset(hd, 0, sizeof(int) * (n + 5)); tot = 0;
for (int i = 0; i < m; ++i) {
scanf("%d%d", &edges[i].u, &edges[i].v);
add_edge(edges[i].u, edges[i].v);
}
tim = cnt = top = 0;
memset(scc, 0, sizeof(int) * (n + 5));
memset(dfn, 0, sizeof(int) * (n + 5));
for (int i = 1; i <= n; ++i)
if (scc[i] == 0)
tarjan(i);
memset(hd, 0, sizeof(int) * (cnt + 5)); tot = 0;
memset(oe, 0, sizeof(int) * (cnt + 5));
memset(mx, 0, sizeof(int) * (cnt + 5));
for (int i = 0; i < m; ++i) {
int u = scc[edges[i].u];
int v = scc[edges[i].v];
if (u != v)
add_edge(v, u), oe[u] += 1;
}
for (int i = 1; i <= n; ++i)
mx[scc[i]] = max(mx[scc[i]], w[i]);
bfs();
for (int i = 0, u, x; i < k; ++i) {
scanf("%d%d", &u, &x);
printf("%lld\n", 1LL * x * mx[scc[u]]);
}
}
return 0;
}



样例输入

2 3 1 0 2 0 2 2 1 0

题解
很像 Qtree 啊 所以一样hintai
树链剖分
单点修改,查询区间内值为x的数
考虑如何实现???
如果x比较少,完全可以建几棵线段树来实现,就好比 20% 的数据,颜色种类不超过 5
每次修改一个颜色,就是在该颜色线段树内 +1,原颜色线段树内 -1
颜色种类多了怎么办?
暴力:开100个树状数组,和刚才没什么区别
如果线段树在每一个节点上维护一个100的数组
合并的时候可以直接暴力统计节点次数,这样代价是区间长度
如果每一位枚举则是n*100
每一层访问的点是n的,一共log层
复杂度 O(nlogn)
继续优化:
离线操作,只需要建一棵线段树
操作分类,与同一种颜色有关的操作放到一起
所有操作次数相加就是2m
所以操作还是o(m)
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int getint()
{
int r = 0, c = getchar();
for (; c < 48; c = getchar());
for (; c > 47; c = getchar())
r = r * 10 + c - 48;
return r;
}
const int mxc = 100005;
const int mxn = 100005;
const int mxm = 200005;
int n, m, c;
int tt;
int hd[mxn];
int to[mxm];
int nt[mxm];
inline void addedge(int x, int y)
{
nt[++tt] = hd[x], to[tt] = y, hd[x] = tt;
nt[++tt] = hd[y], to[tt] = x, hd[y] = tt;
}
struct data
{
int k, x, y;
data() {} ;
data(int a, int b, int c)
: k(a), x(b), y(c) {} ;
};
int color[mxn];
#include <vector>
vector<data> vec[mxc];
int tim;
int dfn[mxn];
int top[mxn];
int fat[mxn];
int dep[mxn];
int son[mxn];
int siz[mxn];
void dfs1(int u, int f)
{
siz[u] = 1;
son[u] = 0;
fat[u] = f;
dep[u] = dep[f] + 1;
for (int i = hd[u], v; i; i = nt[i])
if (v = to[i], v != f)
{
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
son[u] = v;
}
}
void dfs2(int u, int f)
{
dfn[u] = ++tim;
if (son[f] == u)
top[u] = top[f];
else
top[u] = u;
if (son[u])
dfs2(son[u], u);
for (int i = hd[u], v; i; i = nt[i])
if (v = to[i], v != f && v != son[u])
dfs2(v, u);
}
int bit[mxn];
inline void add(int p, int v)
{
for (; p <= n; p += p & -p)
bit[p] += v;
}
inline int ask(int l, int r)
{
int sum = 0; --l;
for (; r; r -= r & -r)
sum += bit[r];
for (; l; l -= l & -l)
sum -= bit[l];
return sum;
}
int ans[mxn];
signed main()
{
int cas = getint();
while (cas--)
{
n = getint();
m = getint();
for (int i = 1; i <= n; ++i)
vec[color[i] = getint()].push_back(data(0, i, +1));
c = 0;
for (int i = 1; i <= n; ++i)
c = max(c, color[i]);
memset(hd, 0, sizeof(int) * (n + 5)); tt = 0;
for (int i = 1; i < n; ++i)
{
int x = getint();
int y = getint();
addedge(x, y);
}
for (int i = 1; i <= m; ++i)
{
if (getint() == 1)
{
int p = getint();
int a = color[p];
int b = color[p] = getint();
vec[a].push_back(data(0, p, -1));
vec[b].push_back(data(0, p, +1));
}
else
{
int x = getint();
int y = getint();
int k = getint();
vec[k].push_back(data(i, x, y));
}
}
dfs1(1, 0);
dfs2(1, 0);
memset(ans, -1, sizeof ans);
for (int k = 1; k <= c; ++k)
{
int sz = vec[k].size();
memset(bit, 0, sizeof bit);
for (int i = 0; i < sz; ++i)
{
const data &d = vec[k][i];
ans[d.k] = 0;
if (d.k == 0)
add(dfn[d.x], d.y);
else
{
int a = d.x, ta = top[a];
int b = d.y, tb = top[b];
while (ta != tb)
{
if (dep[ta] >= dep[tb])
ans[d.k] += ask(dfn[ta], dfn[a]), ta = top[a = fat[ta]];
else
ans[d.k] += ask(dfn[tb], dfn[b]), tb = top[b = fat[tb]];
}
if (dep[a] <= dep[b])
ans[d.k] += ask(dfn[a], dfn[b]);
else
ans[d.k] += ask(dfn[b], dfn[a]);
}
}
}
for (int i = 1; i <= m; ++i)
if (ans[i] >= 0)
printf("%d\n", ans[i]);
for (int i = 1; i <= c; ++i)
vec[i].clear();
tim = 0;
}
return 0;
}
