P 2016 战略游戏
先喂一波题意:给一颗树,选最小点覆盖(选定一个点,与它相连的那些点也将被覆盖)
1. 树形dp
#include <cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1505;
int n,cur;
//树形dp
int dp[1505][4];
//链式前向星存边
int to[maxn*2],nxt[maxn*2],head[maxn];
void add(int x,int y){
to[++cur]=y;
nxt[cur]=head[x];
head[x]=cur;
}
void dfs(int u,int fa){
dp[u][0]=0;dp[u][1]=1;
for(int i=head[u];i;i=nxt[i]){
if(to[i]==fa) continue;
dfs(to[i],u);
dp[u][0]+=dp[to[i]][1];
dp[u][1]+=min(dp[to[i]][0],dp[to[i]][1]);
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int id,k;
cin>>id>>k;
for(int j=1;j<=k;j++){
int a;
cin>>a;
add(id,a);
add(a,id);
}
}
dfs(0,-1);
cout<<min(dp[0][0],dp[0][1])<<endl;
return 0;
}
2. 贪心
秀无敌啊
策略 如下:从叶子开始处理,对每一个正在处理的节点,查看它是否被点亮,如果未被点亮,点亮它的父亲(点父亲总不会比点亮自己差,父亲可能连接更多的点,如果已经被点亮,就暂时不需点亮它的父亲(不一定需要),再看它父亲的其他节点;当发现它父亲的儿子已经全被点亮而它父亲还未被点亮,将父亲放入队列后续判断。
最后统计点亮个数即可。
#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=1505;
int n,cur,root,c[maxn],fa[maxn],ans;
bool li[maxn];
vector<int> v[maxn];
queue<int> q;
int main(){
cin>>n;
for(int i=0;i<n;i++) fa[i]=-1;
for(int i=1;i<=n;i++){
int id,k;
cin>>id>>k;
c[id]+=k;
for(int j=1;j<=k;j++){
int x;
cin>>x;
fa[x]=id;
v[id].push_back(x);
}
}
for(int i=0;i<n;i++){
if(v[i].size()==0){
q.push(i);
}
if(fa[i]==-1) root=i;
}
//n=1要特判一下 因为n=1进不了循环
if(n==1) {
cout << 1<<endl;
return 0;
}
while(q.front()!= root){
int now=q.front();
q.pop();
c[fa[now]]--;
if(!li[now]) li[fa[now]]=1;
if(!c[fa[now]]) q.push(fa[now]);
}
for(int i=0;i<n;i++)
if(li[i]) ans ++ ;
cout << ans<<endl;
return 0;
}
3 匈牙利算法
小结论:
最小覆盖数==最大匹配数(如果是无向图 == 最大匹配数/2)
#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=7000;
int n,ans,cnt,head[maxn],to[maxn*2],match[maxn*2],nxt[maxn*2];
bool vis[maxn];
void add(int x,int y){
to[++cnt]=y;
nxt[cnt]=head[x];
head[x]=cnt;
}
bool dfs(int x){
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!vis[y]){
vis[y]=1;
if(match[y]==-1|| dfs(match[y])){
match[y]=x;
return 1;
}
}
}
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int id,k;
cin>>id>>k;
for(int j=1;j<=k;j++){
int x;
cin>>x;
add(x,id);
add(id,x);
}
}
for(int i=0;i<n;i++) match[i]=-1;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++) vis[j]=0;
if(dfs(i)) ans ++;
}
cout << ans/2 <<endl;
return 0;
}
P1525
两种做法-
1. 并查集
并查集带权,实现基于贪心的思想。把冲突值递减排列,尽量把值大的两个人划分到不同的监狱。有一个enemy数组,用来存放一个人的敌人团队。
如果冲突,就直接输出,因为已经按照最优策略排列。
// ConsoleApplication5.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
using namespace std;
const int maxn = 1e5 + 5;
int N, M, con[maxn],enemy[maxn],f[maxn];
struct R {
int u, v, w;
bool operator <(const R r) {
return w > r.w;
}
}r[maxn];
void ini() {
for (int i = 1;i <= N;i++) {
f[i] = i;
}
}
//这里都是板子
int find(int x) {
return x == f[x] ? x : f[x]=find(f[x]);
}
void merge(int x, int y) {
f[find(x)] = f[find(y)];
return;
}
int main() {
cin >> N >> M;
for (int i = 1;i <= M;i++) {
cin >> r[i].u >> r[i].v >> r[i].w;
}
ini();
sort(r + 1, r + 1 + M);
for (int i = 1;i <= M;i++) {
//cout << r[i].u << " " << r[i].v << " " << r[i].w << endl;
int p1 = find(r[i].u);
int p2 = find(r[i].v);
//如果这两个人的阵营被安排了,直接输出
if (p1 == p2) {
cout << r[i].w << endl;
return 0;
}
//设置不同阵营
if (!enemy[r[i].u]) {
enemy[r[i].u] = r[i].v;
}
else {
merge(enemy[r[i].u], r[i].v);
}
if (!enemy[r[i].v]) {
enemy[r[i].v] = r[i].u;
}
else {
merge(enemy[r[i].v], r[i].u);
}
}
cout << 0 << endl;
return 0;
}
2 二分图
个人感觉二分图比较好想到,二分答案
教训(L+1<R)不可以乱写,比如这种L<= R就会在检查[x,x]这样的区间的时候GG
// ConsoleApplication5.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 1e5 + 5;
int N, M, R;
struct P {
int nxt, to, w;
bool operator <(const P r) {
return w > r.w;
}
};
int head[maxn*2],cnt;
P edge[maxn * 2];
void addedge(int u, int v, int w) {
edge[++cnt].w = w;
edge[cnt].nxt = head[u];
edge[cnt].to = v;
head[u] = cnt;
}
//搭个架子先,这里是二分染色咯
bool test(int x) {
queue <int> q;
//标记颜色
int color[maxn*2] = {};
for (int i = 1;i <= N;i++) {
if (!color[i]) {
q.push(i);
color[i] = 1;
while (!q.empty()) {
int pp = q.front();
q.pop();
for (int j = head[pp];j;j = edge[j].nxt){
int qq = edge[j].to;
if (edge[j].w >= x) {
if (!color[qq]) {
q.push(qq);
if (color[pp] == 1)
color[qq] = 2;
else color[qq] = 1;
}
else if (color[pp] == color[qq])
return false;
}
}
}
}
}
return true;
}
int main() {
cin >> N >> M;
for (int i = 1;i <= M;i++) {
int u, v, w;
cin >> u >> v >> w;
addedge(u, v, w);
addedge(v, u, w);
R = max(R, w);
}
int L = 0;
R++;
//R++???????????
while (L +1 < R) {
int mid = (L+R)>>1;
//如果可以,缩小范围
if (test(mid)) R = mid;
//不行,扩大
else L = mid;
}
cout << L << endl;
return 0;
}
P1113 是个水题 但还是成功掉坑两次
#include "pch.h"
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 1e4 + 5;
int n,degree[maxn],ans,l[maxn],tim[maxn];
vector<int> v[maxn];
void topo() {
queue<int> q;
for (int i = 1;i <= n;i++) {
if (degree[i] == 0) {
q.push(i);
tim[i] = l[i];
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0;i < v[u].size();i++) {
int nxt = v[u][i];
degree[nxt]--;
if (degree[nxt] == 0) {
q.push(nxt);
}
tim[nxt] = max(tim[u] + l[nxt], tim[nxt]);
}
}
for (int i = 1;i <= n;i++)
ans = max(ans, tim[i]);
}
int main() {
cin >> n;
for (int i = 1;i <= n;i++) {
int id, x,len;
cin >> id >> len;
l[id] = len;
while (cin >> x && x) {
degree[id]++;
v[x].push_back(id);
}
}
topo();
cout << ans << endl;
return 0;
}
P1983 实在是妙题
TOPO
从未经过车站向经过车站连边建图,然后topo一下
(说实话是第一次写拓扑排序呢,离散学得很垃圾…
描述一下流程,免得以后忘记了
(1)队列操作
(2)将入度0的点入队,查找与这个点相连的其他点,让他们的入度–,若入度为0,入队,更新层次个数(是这题目里要维护的最大值),直到队列为空,结束。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 1e5 + 5;
//topo排序
int degree[maxn],n,m,ans;
bool vis[maxn];
vector<int> v[maxn];
struct P {
int id, cnt, book[1005];
}s[1005];
bool edge[1005][1005];
void topo() {
queue<pair<int, int> > q;
for (int i = 1;i <= n;i++) {
if (degree[i] == 0) {
q.push(make_pair(i,1));
}
}
ans = 1;
while (!q.empty()) {
int u = q.front().first, val =q.front().second ;
q.pop();
for (int i = 0;i < v[u].size();i++) {
int nxt = v[u][i];
// cout << "Pass by " << nxt << endl;
degree[nxt]--;
if (degree[nxt] == 0) {
q.push(make_pair(nxt, val + 1));
// cout << "val + 1 = " << val + 1<< endl;
ans = max(ans, val + 1);
}
}
}
}
int tmp[maxn];
void ini() {
for (int i = 1;i <= n;i++)
vis[i] = false;
}
int main() {
cin >> n >> m;
for (int i = 1;i <= m;i++) {
ini();
cin >> s[i].cnt;
for (int j = 1;j <= s[i].cnt;j++) {
//第i列火车的第j个停靠点
int x; cin >> x;
s[i].book[j] = x;
//站点标记,代表这是此次列车的一个停靠点
vis[x] = true;
}
//此次经过的所有站点(始发站 -> 终点站)
for (int j = s[i].book[1];j <= s[i].book[s[i].cnt];j++) {
//若无停靠,入为0
if (vis[j]) continue;
for (int k = 1;k <= s[i].cnt;k++) {
//如果边标记过了,就无需标记,所以这个edge数组是不需要每次清空的
//因为多趟列车,所以会有重复的
if (!edge[j][s[i].book[k]]) {
degree[s[i].book[k]]++;
v[j].push_back(s[i].book[k]);
edge[j][s[i].book[k]] = 1;
}
}
}
}
topo();
cout << ans << endl;
return 0;
}
更新于2020.3.11
今天在洛谷玩耍 看到noip的初试题(没错就是高中同学说不满分就不能出线的那个笔试orz
我
我考了。
78分。
。好菜
不过学会了一个单调栈求大于某个数的第一个位置(lower bound?
)类似吧
思想是这样的,把第一个数字装入栈中。
每次取出顶上的元素,与下一个元素比较,有两种情况:
若栈顶元素小于下一个元素,那么下一个元素就是大于栈顶元素的第一个元素,为栈顶元素更新答案,出栈(直到栈中的元素大于下一个元素或者栈为空),让下一个元素进栈;
若栈顶元素大于下一个元素,把下一个元素也入栈,这样继续查找,找到的答案一定先满足栈顶元素,再符合栈底元素(因为栈底比栈顶大嘛)。
并查集经典好题 食物链
真的好爱洛谷dalao讲解真的好清楚 很棒
//i
#include"pch.h"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#define pi pcos(-1.0)
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define mp make_pair
using namespace std;
const int maxn = 3e5 + 5;
int n, k,f[maxn],ans;
int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}
int main() {
cin >> n >> k;
for (int i = 1;i <= 3*n;i++) {
f[i] = i;
}
for (int i = 1;i <= k;i++) {
int flag, u, v; cin >> flag >> u >> v;
if (u > n || v > n ) { ans++;continue; }
if (flag == 1) {
//同类 如果存在吃与被吃的关系,假
if (find(u + n) == find(v) || find(u) == find(v + n))
ans++;
else {
f[find(u)] = find(v);
f[find(u + n)] = find(v + n);
f[find(u + n + n)] = find(v + n + n);
}
}
else {
//u吃v,如果存在反吃关系或同类关系,假
if (find(v) == find(u+n) || find(u) == find(v))
ans++;
else {
f[find(v + n)] = find(u);
f[find(v + n + n)] = find(u + n);
f[find(v)] = find(u + n + n);
}
}
}
cout << ans << endl;
return 0;
}