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点
对于环上将要被访问的点,有几种选择:
- 访问
- 不访问,回溯到in点,并访问y点(条件:所有的绿点都己经被访问(否则绿点将无法到达))
- 不访问,回溯到红点,并访问该红点的未被访问的子结点(条件:沿路上所有红点的子结点均被访问(原因同上))
上面几种选择,当然是要先满足条件再看哪一个最优,判断:
变量说明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不驻兵时,它的子结点必须驻兵因此
特别地,当j被强制要求不能驻兵时,f[i][0]不存在,赋值为无穷大
当i驻兵时,i的子结点可驻兵或不驻兵
关于国王强制要求的处理:
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;
}
未完……