目录
A:LightOJ - 1251 Forming the Council
C:LightOJ - 1291 Real Life Traffic
D:LightOJ - 1074 Extended Traffic
E:LightOJ - 1108 Instant View of Big Bang
F:LightOJ - 1221 Travel Company
G:LightOJ - 1002 Country Roads
H:LightOJ - 1029 Civil and Evil Engineer
A:LightOJ - 1251 Forming the Council:题目大意:一共有N个选民,M个参选者,一个选民有两个意向,对于+ 表示希望这个人当选。 - 表示希望这个人落选,至少满足一条的结果,对于这个选民来讲就是满足的。问是否有一个可行解,使得所有选民都满足。如果有,输出当选的人的编号。思路:很明显的2-Sat问题。首先我们将一个参选者的当选和不当选进行拆点。i代表第i个人当选了,i+n代表这个人没有当选。那么这个问题就变成了一个经典的2-Sat问题。
#include<stdio.h>
#include<string.h>
#include<vector>
#include<queue>
using namespace std;
int output[40005];
int vis[70005];
int low[70005];
int dfn[70005];
int print[70005];
int stack[70005];
int color[70005];
int pos[70005];
int degree[70005];
vector<int >mp[70005];
vector<int >mp2[70005];
int n,m,sig,cnt,tot,cont;
void add(int x,int y)
{
mp[x].push_back(y);
}
void top()
{
memset(print,0,sizeof(print));
queue<int >s;
for(int i=1;i<=sig;i++)
{
if(degree[i]==0)
{
s.push(i);
}
}
while(!s.empty())
{
int u=s.front();
if(print[u]==0)
{
print[u]=1;print[pos[u]]=2;
}
s.pop();
for(int i=0;i<mp2[u].size();i++)
{
int v=mp2[u][i];
degree[v]--;
if(degree[v]==0)s.push(v);
}
}
cont=0;
for(int i=1;i<=n;i++)if(print[color[i]]==1)output[cont++]=i;
}
void Tarjan(int u)
{
vis[u]=1;
dfn[u]=low[u]=cnt++;
stack[++tot]=u;
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)Tarjan(v);
if(vis[v]==1)low[u]=min(low[u],low[v]);
}
if(low[u]==dfn[u])
{
sig++;
do
{
vis[stack[tot]]=-1;
color[stack[tot]]=sig;
}
while(stack[tot--]!=u);
}
}
int Slove()
{
sig=0;
cnt=1;
tot=-1;
memset(degree,0,sizeof(degree));
memset(stack,0,sizeof(stack));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(vis,0,sizeof(vis));
memset(color,0,sizeof(color));
for(int i=1;i<=n*2;i++)
{
if(vis[i]==0)
{
Tarjan(i);
}
}
for(int i=1;i<=n;i++)
{
if(color[i]==color[i+n])return 0;
pos[color[i]]=color[i+n];
pos[color[i+n]]=color[i];
}
for(int i=1;i<=n*2;i++)
{
for(int j=0;j<mp[i].size();j++)
{
int v=mp[i][j];
if(color[i]!=color[v])
{
degree[color[i]]++;
mp2[color[v]].push_back(color[i]);
}
}
}
top();
return 1;
}
int main()
{
int t;
int kase=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&m,&n);
for(int i=1;i<=60000;i++)mp[i].clear(),mp2[i].clear();
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
int xx=x;int yy=y;
if(x<0)x=-x;
if(y<0)y=-y;
if(xx>0&&yy>0)add(x+n,y),add(y+n,x);
if(xx>0&&yy<0)add(x+n,y+n),add(y,x);
if(xx<0&&yy>0)add(x,y),add(y+n,x+n);
if(xx<0&&yy<0)add(x,y+n),add(y,x+n);
}
int ans=Slove();
printf("Case %d: ",++kase);
if(ans==1)
{
printf("Yes
");
printf("%d",cont);
for(int i=0;i<cont;i++)
{
printf(" %d",output[i]);
}
printf("
");
}
else printf("No
");
}
}
B:LightOJ - 1063 Ant Hills:题目大意:给你n个点,m条边,然后让你破坏某个点使得至少其他两个点不连通,就是用强连通求割点个数。思路:求割点的模板。
#include<set>
#include<stack>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define endl '
'
using namespace std;
const int maxn = 10010;
vector<int> v[maxn];
stack<int> s;
set<int> cut;
int low[maxn],DFN[maxn],vis[maxn];
int Belong[maxn];//Belong数组的值是1~scc
int num[maxn];//各个强连通分量包含点的个数,数组编号1~scc
int id;
int scc;//强连通分量的个数
void Tarjan(int x,int fa)
{
low[x] = DFN[x] = ++id;
vis[x] = 1;
s.push(x);
int son = 0;
for(int i=0;i<v[x].size();i++)
{
int xx = v[x][i];
if(!DFN[xx])
{
Tarjan(xx,x);
low[x]=min(low[x],low[xx]);
//求割点
if (fa == -1 && son != 0)
cut.insert(x);
if (fa != -1 && low[xx] >= DFN[x])
cut.insert(x);
son++;
}
else if(vis[xx])
low[x]=min(low[x],DFN[xx]);
}
if(low[x]==DFN[x])
{
scc++;
while(s.size())
{
int xx=s.top();
s.pop();
num[scc]++;
Belong[xx] = scc;
vis[xx] = 0;
if(xx==x)
break;
}
}
}
void solve(int n)
{
for(int i = 1;i <= n;i++)
if(!DFN[i])
Tarjan(i,-1);
}
void init()
{
memset(DFN,0,sizeof(DFN));
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
while(s.size())
s.pop();
cut.clear();
for(int i=0;i<=maxn;i++)
v[i].clear();
scc = id = 0;
}
int main(void)
{
int T,n,m;
scanf("%d",&T);
int cas = 1;
while(T--)
{
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
solve(n);
printf("Case %d: %d
",cas++,cut.size());
}
return 0;
}
C:LightOJ - 1291 Real Life Traffic:题意:最少添加几条边 可以使全图边双连通。思路:简单的缩点问题。通过Tarjian求出各个双连通分支后缩点,统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
#include <vector>
#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
const int maxn = 10010;
vector <int> a[maxn];
int pre[maxn];
int low[maxn];
int bccno[maxn];
int dfs_clock;
int bcc_cnt;
int bri;
int n, m;
int degree[maxn];
stack <int> S;
void dfs(int u, int fa)
{
low[u] = pre[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < a[u].size(); i++)
{
int v = a[u][i];
if (v == fa)
continue;
if (!pre[v])
{
dfs(v, u);
low[u] = min(low[u], low[v]);
}
else if (v != fa)
low[u] = min(low[u], pre[v]);
}
if (low[u] == pre[u])
{
bcc_cnt++;
while (1)
{
int x = S.top(); S.pop();
bccno[x] = bcc_cnt;
if (x == u)
break;
}
}
}
void find_bcc()
{
memset(pre, 0, sizeof(pre));
memset(bccno, 0, sizeof(bccno));
dfs_clock = bcc_cnt = bri = 0;
for (int i = 0; i < n; i++)
if (!pre[i])
dfs(i, -1);
}
int main()
{
int cas = 1;
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &n, &m);
for (int i = 0; i <= n; i++)
a[i].clear();
while (m--)
{
int u, v;
scanf("%d %d", &u, &v);
a[u].push_back(v);
a[v].push_back(u);
}
find_bcc();
memset(degree, 0, sizeof(degree));
for (int u = 0; u < n; u++)
{
for (int i = 0; i < a[u].size(); i++)
{
int v = a[u][i];
if (bccno[u] != bccno[v])
{
degree[bccno[u]]++;
degree[bccno[v]]++;
}
}
}
int ans = 0;
for (int i = 1; i <= bcc_cnt; i++)
if (degree[i] == 2)
ans++;
printf("Case %d: %d
", cas++, (ans + 1) / 2);
}
return 0;
}
D:LightOJ - 1074 Extended Traffic:题目大意:就是有n个点,1--n的顺序给出每个点都有一定的拥挤值,然后给你m条边,这m条边表示相连着,着m条边的边权值是目的地点的值减去出发点的值的3次方,注意,这是有向图,然后再给你q个点,去求1号点到这q个点的最小值,如果值小于3,或者不能够到达,那就输出 ‘?’思路:可以用spfa算法判断负环。SPFA就是Bellman 的队列优化,所以每个点最多进入到队列中n次就可以找到最短路,如果是进入到队列里面大于等于n次就说明存在负权回路,在这道题目中,负权回路上的所有的点都是不满足题意的,所以就输出 "?"。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 205;
int cnt;
int t, n, m;
typedef struct {
int nxt, to, w;
}edge;
int head[maxn], num[maxn], dis[maxn], cir[maxn];
edge edges[maxn * 100];
int v[maxn], vis[maxn];
void add(int from, int to, int w) {
edges[++cnt].to = to;
edges[cnt].w = w;
edges[cnt].nxt = head[from];
head[from] = cnt;
}
void dfs(int id) {
cir[id] = true;
for (int i = head[id]; i; i = edges[i].nxt) {
if (!cir[edges[i].to])
dfs(edges[i].to);
}
}
void spfa(int s) {
queue<int>st;
st.push(s);
num[1] = 1; vis[1] = 1; dis[1] = 0;
while (!st.empty()) {
int cur = st.front(); st.pop();
vis[cur] = false;
if (cir[cur])continue;
for (int i = head[cur]; i; i = edges[i].nxt) {
int v = edges[i].to;
if (dis[v] > edges[i].w + dis[cur]) {
dis[v] = edges[i].w + dis[cur];
if (!vis[v]) {
st.push(v);
vis[v] = true;
num[v]++;
if (num[v] == n) {
dfs(v);
}
}
}
}
}
}
void init() {
memset(dis, inf, sizeof(dis));
memset(head, 0, sizeof(head));
memset(num, 0, sizeof(num));
memset(vis, 0, sizeof(vis));
memset(cir, 0, sizeof(cir));
cnt = 0;
}
int main() {
cin >> t;
int x, y;
int qt = 1;
while (t--) {
init();
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
add(x, y, ((v[y] - v[x]) * (v[y] - v[x]) * (v[y] - v[x])));
}
spfa(1);
cout << "Case " << qt++ << ":" << endl;
int ca, query;
cin >> ca;
for (int i = 1; i <= ca; i++) {
cin >> query;
if (dis[query] >= inf || dis[query] < 3 || cir[query]) {
cout << "?" << endl;
}
else {
cout << dis[query] << endl;
}
}
}
}
E:LightOJ - 1108 Instant View of Big Bang:题意:求哪些点可以回到过去 首先负环的点是可以的 一直在付欢里转即可 然后那些可以走到负环的点满足。思路:反向建图 这样负环还是不会变的 只不过负环的方向换了下 原来能到负环的点变成了现在负环能到的点 求出负环标记然后广搜负环能到的点再标记。
//注意没说起点,所以spfa中dis数组的初始化全为零并把所有点放到队列里
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2050;
int cnt;
int t, n, m;
typedef struct {
int nxt, to, w;
}edge;
int head[maxn], num[maxn], dis[maxn], cir[maxn];
edge edges[maxn * 100];
int v[maxn], vis[maxn];
void add(int from, int to, int w) {
edges[++cnt].to = to;
edges[cnt].w = w;
edges[cnt].nxt = head[from];
head[from] = cnt;
}
void dfs(int id) {
cir[id] = true;
for (int i = head[id]; i; i = edges[i].nxt) {
if (!cir[edges[i].to])
dfs(edges[i].to);
}
}
void spfa(int s) {
queue<int>st;
st.push(s);
dis[0] = 0;
for (int i = 0; i < n; i++)
{
dis[i] = 0;
st.push(i);
}
while (!st.empty()) {
int cur = st.front(); st.pop();
vis[cur] = false;
if (cir[cur])continue;
for (int i = head[cur]; i; i = edges[i].nxt) {
int v = edges[i].to;
if (dis[v] > edges[i].w + dis[cur]) {
dis[v] = edges[i].w + dis[cur];
if (!vis[v]) {
st.push(v);
vis[v] = true;
num[v]++;
if (num[v] >= n) {
dfs(v);
}
}
}
}
}
}
void init() {
memset(dis, 0, sizeof(dis));
memset(head, 0, sizeof(head));
memset(num, 0, sizeof(num));
memset(vis, 0, sizeof(vis));
memset(cir, 0, sizeof(cir));
cnt = 0;
}
int main() {
cin >> t;
int x, y, z;
int qt = 1;
while (t--) {
init();
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> z;
add(y, x, z);
}
spfa(0);
cout << "Case " << qt++ << ":" ;
int flag = 0;
for (int i = 0; i < n; i++) {
if (cir[i]) {
cout << ' ' << i;
flag = 1;
}
}
if (flag)
cout << endl;
else
cout << " impossible" << endl;
}
}
F:LightOJ - 1221 Travel Company:题目: 旅游公司推出了一种新的旅游路线,这里有n个城市,编号为0~n-1,有m条路线,每行输入u v in out 4个变量,u v 代表编号为u和v的两个城市相连,in代表旅游公司在这条路线上的收入,out代表支出,最开始一行给出一个利润率p(收入/支出),公司要求找到一条循环路线(即一个环),使得总利率大于p。
思路: 我们可以根据题意写出这么一条原始的公式:(in[0]+in[1]+...+in[k])/(out[0]+out[1]+...+out[k])>p
即p*(out[0]+out[1]+...+out[k])<(in[0]+in[1]+....in[k]), 故,就是对于这个环中的任意一条边,都要求p*out[i]-in[i]<0,即在图中存在负环。用Spfa算法,因为最短路最多对边松弛n-1次就可以求出来,那么如果一个点入队次数等于n,说明存在负环,即不存在最短路。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <list>
using namespace std;
const int maxn = 50100;
const int INF = 0x3f3f3f3f;
int n, m, dis[maxn], vis[maxn], intime[maxn];
vector < pair<int, int> > eg[maxn];
int spfa(int src)
{
queue <int> Q;
memset(dis, INF, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(intime, 0, sizeof(intime));
dis[src] = 0;
Q.push(src);
while (!Q.empty()){
int u = Q.front(); Q.pop();
vis[u] = 0; intime[u]++;
if (intime[u] > n)
return 1;
int len = eg[u].size();
for (int i = 0; i < len; i++){
int v = eg[u][i].first;
int w = eg[u][i].second;
if (dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if (!vis[v]){
vis[v] = 1;
Q.push(v);
}
}
}
}
return 0;
}
int main()
{
int T, p, cas = 1;
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &n, &m, &p);
for (int i = 0; i <= n; i++)
eg[i].clear();
while (m--){
int u, v, in, out;
scanf("%d%d%d%d", &u, &v, &in, &out);
int tem = out * p - in;
eg[u].push_back(make_pair(v, tem));
}
printf("Case %d: ", cas++);
int flag = 1;
for (int i = 0; i < n; i++){
if (spfa(i)){
flag = 0;
printf("YES
");
break;
}
}
if (flag)
printf("NO
");
}
return 0;
}
G:LightOJ - 1002 Country Roads:题目:一共n个城市 ,m 条路, 最后还有一个城市编号 t, 求从t 城市到所有城市的每一条路径中最长边中最小的那条边, 如果到不了 ,输出"Impossible"。思路:具体描述可看https://blog.csdn.net/qq_44765711/article/details/100526905。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <list>
using namespace std;
const int maxn = 50100;
const int INF = 0x3f3f3f3f;
int n, m, x, dis[maxn], vis[maxn], intime[maxn];
vector < pair<int, int> > eg[maxn];
void spfa(int src)
{
queue <int> Q;
memset(dis, INF, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[src] = 0;
Q.push(src);
while (!Q.empty()){
int u = Q.front(); Q.pop();
vis[u] = 0;
int len = eg[u].size();
for (int i = 0; i < len; i++){
int v = eg[u][i].first;
int w = eg[u][i].second;
int tmp = max(dis[u], w);
if (dis[v] > tmp){
dis[v] = tmp;
if (!vis[v]){
vis[v] = 1;
Q.push(v);
}
}
}
}
}
int main()
{
int T, cas = 1;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; i++)
eg[i].clear();
while (m--){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
eg[u].push_back(make_pair(v, w));
eg[v].push_back(make_pair(u, w));
}
scanf("%d", &x);
printf("Case %d:
", cas++);
spfa(x);
for (int i = 0; i < n; i++) {
if(dis[i]>=INF)
printf("Impossible
");
else
printf("%d
", dis[i]);
}
}
return 0;
}
H:LightOJ - 1029 Civil and Evil Engineer:求最小生成树和最大生成树
#include <stdio.h>
#include <iostream>
#include <string.h>
const int inf = 1e6;
using namespace std;
int map[1000][1000],Map[1000][1000];
int dis[10000],vist[10000];
int MIN = 0,MAX = 0;
int x;
void minn()
{
for(int i = 0; i<=x; i++)
{
vist[i] = 0;
dis[i] = map[0][i];
}
vist[0] = 1 ;
for(int i = 1; i<=x; i++)
{
int min = inf,pos = 0;
for(int j = 0; j<=x; j++)
{
if(vist[j] == 0 && dis[j] < min)
{
min = dis[j];
pos = j;
}
}
MIN += min;
vist[pos] = 1 ;
for(int j = 0; j<=x; j++)
{
if(vist[j] == 0 && dis[j] > map[pos][j])
{
dis[j] = map[pos][j];
}
}
}
}
void maxx()
{
for(int i = 0; i<=x; i++)
{
vist[i] = 0;
dis[i] = Map[0][i];
}
vist[0] = 1 ;
for(int i = 1; i<=x; i++)
{
int min = 0,pos = 0;
for(int j = 0; j<=x; j++)
{
if(!vist[j] && dis[j] > min)
{
min = dis[j];
pos = j;
}
}
if(pos==0)
break;
MAX += min;
vist[pos] = 1 ;
for(int j = 0; j<=x; j++)
{
if(vist[j] == 0&& dis[j] < Map[pos][j])
{
dis[j] = Map[pos][j];
}
}
}
}
int main()
{
int a,b,c,t;
int cou = 1;
scanf("%d",&t);
while(t--)
{
scanf("%d",&x);
memset(Map,0,sizeof(Map));
memset(map,inf,sizeof (map));
while(~scanf("%d%d%d",&a,&b,&c))
{
if(a==0 &&b==0 &&c==0)
break;
if(c<map[a][b])
map[a][b] = map[b][a] = c;
if(c>Map[a][b])
Map[a][b] = Map[b][a] = c;
}
MIN = 0;
MAX = 0;
minn ();
maxx ();
// printf ("%d %d
",MIN,MAX);
printf ("Case %d: ",cou);
cou++;
if ((MIN + MAX) % 2 == 0)
printf ("%d
",(MIN + MAX)/2);
else printf ("%d/2
",MAX+MIN);
}
return 0;
}