一些个人认为不错的图论题。
T1:https://www.luogu.org/problemnew/show/P1197
这道题可能一看到没什么思路,但我们可以逆向思维一下,也就是正难则反,把炸毁点想成修建,然后类似于时光倒流那样跑并查集就可以了。
code:
#include<iostream>
#include<cstring>
using namespace std;
const int N=4e5+10, M=2e5+10;
struct edge {
int v,next;
} e[M<<1];
int n, m, k, ans[N], saven[N], nosave[N];
int cnt, head[N];
int f[N];
bool saveb[N];
void init() {
memset(saveb, true, sizeof(saveb));
for (int i=1; i<=n; i++)
f[i]=i;
return ;
}
void add(int a, int b) {
e[++cnt].v=b;
e[cnt].next=head[a];
head[a]=cnt;
return ;
}
int find(int x) {
if (x==f[x]) return f[x];
else return f[x]=find(f[x]);
}
void merge(int a, int b) {
int f1=find(a), f2=find(b);
if (f1!=f2) f[f2]=f1;
return ;
}
bool query(int a, int b) {
int f1=find(a), f2=find(b);
if (f1!=f2) return false;
else return true;
}
int main() {
int x, y, tot=0;
cin >> n >> m;
init();
for (int i=1; i<=m; i++) {
cin >> x >> y;
add(x+1, y+1);
add(y+1, x+1);
}
cin >> k;
for (int i=1; i<=k; i++) {
cin >> x;
nosave[i]=x+1;
saveb[x+1]=false;
}
for (int i=1; i<=n; i++)
if (saveb[i]) saven[++tot]=i;
int tmp=0;
for (int i=1; i<=tot; i++) {
int u=saven[i];
for (int j=head[u]; j; j=e[j].next) {
int v=e[j].v;
if (!query(u, v) && saveb[v]) {
merge(u, v);
tmp++;
}
}
}
ans[k]=tot-tmp;
for (int i=k; i>=1; i--) {
int u=nosave[i];
tmp=0;
for (int j=head[u]; j; j=e[j].next) {
int v=e[j].v;
if (saveb[v] && !query(u, v)) {
merge(u, v);
tmp++;
}
}
saveb[u]=true;
ans[i-1]=ans[i]-(tmp-1);
}
for (int i=0; i<=k; i++)
cout << ans[i] << endl;
return 0;
}
T2:https://www.luogu.org/problemnew/show/P4366#sub
这道题很明显dij是过不去的(SPFA更不用说了),而主要的复杂度就是在于边数太多上,所以我们想怎么优化边数。
这里举例来说:3xor6=5,5的二进制表示为101,可以发现101可以由100和1相加得到,而100是3xor7的值,1则是7xor6的值,即3xor6=3xor7+7xor6,也就是我们可以只考虑3到7的边和7到6的边。那么可以得到这样的一个结论:
对于点i和j,满足ixorj=2^k(k为非负整数),则i和j之间的边我们就需要考虑。变形一下得到j=ixor2^k,k非负整数,j小于等于n,这样总边数就变成了M+nlogn,可以放心的跑dj了。
还有一个需要注意的地方就是编号为0的点也要考虑,因为若中间点大于n的话,可以用零来代替,可以结合上面的例子自行考虑一下。
code:
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
typedef pair<int, int> pii;
const int N=1e5+10, M=3e6+10, INF=0x3f3f3f3f;//M要开大点
struct edge {
int v, w, next;
} e[M];
priority_queue<pii, vector<pii>, greater<pii> > Q;
int n, m, c, sx, sy;
int cnt, head[N];
int dis[N];
bool vis[N];
int read() {
int x=0, w=1;
char ch=0;
while (ch<'0' || ch>'9') {
if (ch=='-') w=-1;
ch=getchar();
}
while (ch>='0' && ch<='9') {
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*w;
}
void add(int u, int v, int w) {
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
return ;
}
void dijkstra(int s) {
dis[s]=0;
Q.push(make_pair(dis[s], s));
while (!Q.empty()) {
int u=Q.top().second;
Q.pop();
if (vis[u]) continue;
vis[u]=true;
for (int i=head[u]; i; i=e[i].next) {
int v=e[i].v;
if (dis[v]>dis[u]+e[i].w) {
dis[v]=dis[u]+e[i].w;
Q.push(make_pair(dis[v], v));
}
}
}
return ;
}
int main() {
memset(dis, INF, sizeof(dis));
int x, y, z;
n=read(), m=read(), c=read();
for (int i=1; i<=m; i++) {
x=read(), y=read(), z=read();
add(x, y, z);
}
sx=read(), sy=read();
for (int i=0; i<=n; i++) {
for (int j=0; j<=25; j++) {
int to=i^(1<<j);
if (to<=n) add(i, to, (1<<j)*c);
}
}
dijkstra(sx);
printf("%d", dis[sy]);
return 0;
}