2018NOIP提高组Day2(含旅行Tarjan解法) 真·超详细


TIPS:数据包下载(NOI官网)

更新中……

P5022 旅行(Tarjan)

题目传送门:

普通版
数据加强

思路

怎么说这道题呢?看起来挺简单,但是细节问题很多。
先说说m=n-1的情况:就是把每个点的子节点按从小到大编号排序然后从1号点开始遍历即可,60分到手
麻烦的是m=n的情况:就是一棵基环树,大部分题解给的都是O(n2)的暴力枚举。由于我不大熟悉基环树,第一个想到的是Tarjan,结果92分(WA了两个点)搜题解都几乎没见过Tarjan的,但是以为用Tarjan的做法很简单就“独行其道”了,结果不知道掉了多少头发
说正事:
图就是一棵树再加上一条边,也就是有且只有一个环,普通的点很简单,按照m=n-1的方法即可,问题是和环有关的点,看图:
在这里插入图片描述对于这个环,我们将第一个被访问的结点称为in结点,将环上已经被访问的结点标记为红色,它们的子结点标记为绿色,in的子结点(已排序)中(第一个 环上的点的)下一个点(有点绕,前面括号是为了断句)记为y点
对于环上将要被访问的点,有几种选择:

  1. 访问
  2. 不访问,回溯到in点,并访问y点(条件:所有的绿点都己经被访问(否则绿点将无法到达))
  3. 不访问,回溯到红点,并访问该红点的未被访问的子结点(条件:沿路上所有红点的子结点均被访问(原因同上))

上面几种选择,当然是要先满足条件再看哪一个最优,判断:
变量说明x:当前结点(未访问),y:见上,z:从x点回溯到in的路上第一个未访问的绿点(其实这里表达不大好,绿点并不在该路径上,而是通过红点连接到环的旁边)
当z==-1时,z不存在,当y==(1<<29)时,表示曾经已经回溯到y点,当普通树处理即可

	if(tar[x] != dfn[x] && y != (1 << 29)){
		if(z != -1){
			if(x > z)return;
		}
		else if(y != -1){
			if(x > y)return;
		}
	}

那么如何判断一个点在不在环上呢?这就需要tarjan了,不会的先自学,dfs序存储在dfn数组里,tar[]就是“追溯值”。当dfn[i]!=tar[i]时,i点在环上,特别地,dfn[in]==tar[in]这就需要我们给in点一些特殊待遇了。

代码

92分

其实比赛能拿到92已经很满意了

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <vector>
#include <cstring>
#define nn 5010
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c;
	do
		if((c = getchar()) == '-')sig = -1;
	while(c < '0' || c > '9');
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re;
}

int n , m;
bool vis[nn];
vector <int> son[nn];


void dfs(int x){
	vis[x] = true;
	printf("%d " , x);
	for(int i = 0 ; i < son[x].size() ; i++)
		if(!vis[son[x][i]])
			dfs(son[x][i]);
}
int dfn[nn] , tar[nn];
void tarjan(int x , int dep , int fa){
	if(dfn[x] != 0)
		return;
	tar[x] = dfn[x] = dep;
	for(int i = 0 ; i < son[x].size() ; i++){
		tarjan(son[x][i] , dep + 1 , x);
		if(tar[son[x][i]] < tar[x] && son[x][i] != fa)
			tar[x] = tar[son[x][i]];
	}
}
int y = -1;
void dfs2(int x) {
	if(x == y)
		y = (1 << 29);
//	cout << x << '\t' << y << endl;
	if(tar[x] != dfn[x]){
		if(y < x){
			return;
		}
	}
	
	vis[x] = true;
	printf("%d " , x);
	if(y == -1){
		for(int i = 0 ; i < son[x].size() ; i++){
			if(tar[son[x][i]] != dfn[son[x][i]])
				y = son[x][i];
		}
	}
		
		
	for(int i = 0 ; i < son[x].size() ; i++)
		if(!vis[son[x][i]])
			dfs2(son[x][i]);
}
int main(){
	n = read();	m = read();
	for(int i = 1 ; i <= m ; i++){
		int u , v;
		u = read() , v = read();
		son[u].push_back(v);
		son[v].push_back(u);
	}
	for(int i = 1 ; i <= n ; i++)
		sort(son[i].begin() , son[i].end());
	if(m == n - 1)
		dfs(1);
	else{
		tarjan(1 , 1 , 0);
//		for(int i = 1 ; i <= n ; i++)
//			cout << tar[i] << '\t';
//		cout << endl;
		memset(vis , 0 , sizeof(vis));
		dfs2(1);
	}
	return 0;
} 

100分

为了这8分掉了多少头发QAQ
说明:以下做法效率应该为O(n),改一下数组大小依然可以通过洛谷数据加强版

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <vector>
#include <cstring>
#define nn 5010
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c;
	do
		if((c = getchar()) == '-')sig = -1;
	while(c < '0' || c > '9');
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re;
}

int n , m;
bool vis[nn];
int in;
vector <int> son[nn];

void dfs(int x){
	vis[x] = true;
	printf("%d " , x);
	for(int i = 0 ; i < son[x].size() ; i++)
		if(!vis[son[x][i]])
			dfs(son[x][i]);
}
int dfn[nn] , tar[nn];
int dep = 1;
void tarjan(int x , int fa){
	if(dfn[x] != 0)
		return;
	tar[x] = dfn[x] = dep++;
	for(int i = 0 ; i < son[x].size() ; i++){
		tarjan(son[x][i] , x);
		if(tar[son[x][i]] < tar[x] && son[x][i] != fa){
			if(tar[son[x][i]] == dfn[son[x][i]])
				in = son[x][i];
			tar[x] = tar[son[x][i]];
		}
	}
}
int y = -1;
void dfs2(int x , int z) {
	if(tar[x] != dfn[x] && y != (1 << 29)){
		if(z != -1){
			if(x > z)return;
		}
		else if(y != -1){
			if(x > y)return;
		}
	}
	if(x == y)
		y = (1 << 29);
//	cout << x << '\t' << y << '\t' << z <<endl;
	
	vis[x] = true;
	printf("%d " , x);

	if(y == -1){
		for(int i = 0 ; i < son[x].size() ; i++){
			if(tar[son[x][i]] != dfn[son[x][i]]){
				int j = i + 1;
				while(vis[son[x][j]])j++;
				y = son[x][j];
				break;
			} 
		}
	}//*/
	for(int i = 0 ; i < son[x].size() ; i++)
		if(!vis[son[x][i]]){
			if(dfn[son[x][i]] != tar[son[x][i]] && y != (1 << 29) && x != in){
				int j = i + 1;
				while(j < son[x].size() && (vis[son[x][j]] || dfn[son[x][j]] != tar[son[x][j]] || in == son[x][j])){
					j++;
					if(j >= son[x].size())break;
				}
				if(j < son[x].size())
					dfs2(son[x][i] , son[x][j]);
				else dfs2(son[x][i] , z);
			}
			else dfs2(son[x][i] , z);
		}
}
int main(){
	freopen("input.txt" , "r" , stdin);
	freopen("output.txt" , "w" , stdout);
	n = read();	m = read();
	for(int i = 1 ; i <= m ; i++){
		int u , v;
		u = read() , v = read();
		son[u].push_back(v);
		son[v].push_back(u);
	}
	for(int i = 1 ; i <= n ; i++)
		sort(son[i].begin() , son[i].end());
	if(m == n - 1)
		dfs(1);
	else{
		tarjan(1 , 0);
/*		for(int i = 1 ; i <= n ; i++)
			cout << tar[i] << '\t';
		cout << endl;
*/		memset(vis , 0 , sizeof(vis));
		dfs2(1 , -1);
	}
//	cout << in << endl;
	return 0;
} 

对拍文件

命名:
数据生成:random.cpp
对比:compare.cpp
待测程序(见上):#4.cpp
std程序:std.cpp

数据生成(针对m==n情况,修改下也可生成树)
#include <bits/stdc++.h>
using namespace std;
int random(int r , int l = 1){//范围[l,r]l默认为1
	if(r == l)return l;
	return (long long)rand() * rand() % (r - l) + l;
}
pair<int , int>e[5010];
map<pair<int , int>,bool> h;

int dict[5010];
int main(){
	freopen("input.txt" , "w" , stdout);
	srand((unsigned)time(0));
	
	int n , m;
	n = m = 5000;//可以自调范围
	for(int i = 1 ; i <= n ; i++)
		dict[i] = i;
	random_shuffle(dict + 1 , dict + n + 1);
	
	for(int i = 2 ; i <= n ; i++){
		int fa = random(i - 1);
		e[i] = make_pair(fa , i);
		h[e[i]] = h[make_pair(i , fa)] = 1;
	}
	int x , y;
	do{
		x = random(n) , y = random(n);
	}
	while(x == y || h[make_pair(x , y)]);
	e[1] = make_pair(x , y);
	
	random_shuffle(e + 1 , e + m + 1);
	printf("%d %d\n" , n , m);
	for(int i = 1 ; i <= n ; i++){
		printf("%d %d\n" , dict[e[i].first] , dict[e[i].second]);
	}
	return 0;
}
/*
	freopen("input.txt" , "r" , stdin);
	freopen(".txt" , "w" , stdout);
	
*/
自动对比程序
#include <bits/stdc++.h>
#include <windows.h>
using namespace std;
int main(){
	while(true){
		system("start random.exe");
		Sleep(50);
		system("start #4.exe");
		system("start std.exe");
		Sleep(1500);
		if(system("fc output.txt ans.txt")){
			system("start input.txt");
			cout << "WA!!!" << endl;
			return 0;
		}
	}
	
	return 0;
}
std程序(来自NOI官网)
#include <stdio.h>
#include <string.h>
#include <algorithm>

const int MAXN = 300005;

int n, m;

struct data {
	int u, v, id;
};

bool operator < (const data &a, const data &b) {
	return a.u < b.u || (a.u == b.u && a.v < b.v);
}

data _edges[MAXN * 2];
data *first[MAXN];

int ans[MAXN];
int dfsseq[MAXN];
int visit[MAXN];

int del_e;
int dfs_idx;
bool ok;
bool fail;

#define visit_id (del_e + 1)

void dfs(int x) {
	if (!ok) {
		if (x < ans[dfs_idx]) {
			ok = true;
		} else if (x > ans[dfs_idx]) {
			fail = true;
			return;
		}
	}
	dfsseq[dfs_idx++] = x;
	visit[x] = visit_id;
	for (data *d = first[x]; d->u == x; d++) if (d->id != del_e) {
		int y = d->v;
		if (visit[y] == visit_id) continue;
		dfs(y);
		if (fail) return;
	}
}



int main() {
	freopen("input.txt" , "r" , stdin);
	freopen("ans.txt" , "w" , stdout);
	scanf("%d%d", &n, &m);
	if (n == 1) {
		printf("1\n");
		return 0;
	}
	for (int i = 0; i < m; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		_edges[i * 2] = (data){u, v, i};
		_edges[i * 2 + 1] = (data){v, u, i};
	}
	std::sort(_edges, _edges + 2 * m);
	first[1] = _edges;
	for (int i = 1; i < 2 * m; i++) {
		if (_edges[i].u != _edges[i - 1].u) {
			first[_edges[i].u] = _edges + i;
		}
	}
	ans[0] = 2;
	if (n == m) {
		for (int i = 0; i < m; i++) {
			del_e = i;
			dfs_idx = 0;
			ok = false;
			fail = false;
			dfs(1);
			if (dfs_idx == n && ok && !fail) {
				memcpy(ans, dfsseq, n * sizeof(int));
			}
		}
	} else {
		del_e = -2;
		dfs(1);
		memcpy(ans, dfsseq, n * sizeof(int));
	}
	for (int i = 0; i < n; i++) {
		printf("%d ", ans[i]);
	}
}

P5023 填数游戏

P5024 保卫王国

题目传送门

https://www.luogu.com.cn/problem/P5024

44分思路

经典的树形DP,我们考虑对于国王的每一个问题跑一遍DP,时间复杂度O(nm)44分
做法:设f[i][0/1]表示以i为根的树的最小费用,0表示i结点不驻兵,1则为驻兵
当i不驻兵时,它的子结点必须驻兵因此
f [ i ] [ 0 ] = Σ f [ j ] [ 1 ] ( j 为 i 的 子 结 点 ) f[i][0]=\Sigma f[j][1](j为i的子结点) f[i][0]=Σf[j][1](ji)
特别地,当j被强制要求不能驻兵时,f[i][0]不存在,赋值为无穷大
当i驻兵时,i的子结点可驻兵或不驻兵
f [ i ] [ 1 ] = Σ m i n { f [ j ] [ 1 ] f [ j ] [ 0 ] ( j 为 i 的 子 结 点 ) f[i][1]=\Sigma min\{^{f[j][0]}_{f[j][1]}(j为i的子结点) f[i][1]=Σmin{f[j][1]f[j][0](ji)

关于国王强制要求的处理:

		a = read();
		if((x = read()) == 0)	f[a][1] = inff;
		else					f[a][0] = inff;
		b = read();
		if((y = read()) == 0)	f[b][1] = inff;
		else					f[b][0] = inff;

44分代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define inff (1 << 29)
#define nn 100010
using namespace std;
int read(){
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9'){
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		re = (re << 1) + (re << 3) + c - '0';
		c = getchar();
	}
	return re * sig;
}
struct node{
	int u , nxt;
}ed[nn];
int head[nn];
void addedge(int u , int v){
	static int top = 1;
	ed[top].u = v , ed[top].nxt = head[u] , head[u] = top;
	top++;
	ed[top].u = u , ed[top].nxt = head[v] , head[v] = top;
	top++;
}
inline int minn(int a , int b){
	return a < b ? a : b;
}
int n , m;
int p[nn];

bool vis[nn];
int f[nn][2];

void dfs(int x){
	vis[x] = true;
	for(int i = head[x] ; i ; i = ed[i].nxt)
		if(!vis[ed[i].u])
			dfs(ed[i].u);
	if(f[x][0] != inff){
		f[x][0] = 0;
		for(int i = head[x] ; i ; i = ed[i].nxt)
			if(!vis[ed[i].u]){
				if(f[ed[i].u][1] == inff){
					f[x][0] = inff;
					break;
				}
				f[x][0] += f[ed[i].u][1];
			}
	}
	if(f[x][1] != inff){
		f[x][1] = p[x];
		for(int i = head[x] ; i ; i = ed[i].nxt)
			if(!vis[ed[i].u])
				f[x][1] += minn(f[ed[i].u][0] , f[ed[i].u][1]);
	}
	vis[x] = false;
}
signed main(){
	n = read() , m = read() , read();
	for(int i = 1 ; i <= n ; i++)
		p[i] = read();
	for(int i = 1 ; i < n ; i++)
		addedge(read() , read());
	for(int i = 1 ; i <= m ; i++){
		memset(f , 0 , sizeof(f));
		int a , b , x , y;
		a = read();
		if((x = read()) == 0)	f[a][1] = inff;
		else					f[a][0] = inff;
		b = read();
		if((y = read()) == 0)	f[b][1] = inff;
		else					f[b][0] = inff;
		
		if(x == 0 && y == 0){
			int j;
			for(j = head[a] ; j ; j = ed[j].nxt)
				if(ed[j].u == b){
					printf("-1\n");
					break;
				}
			if(j)continue;
		}
			
		dfs(1);
		printf("%d\n" , minn(f[1][0] , f[1][1]));
	}
	return 0;
}

未完……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值