食物链(POJ 1182):
题意: 有 个动物,编号为 ,每个动物都是是 中的一种,但是我们并不知道它们是哪一种。现在给出 句话, 表示 和 是同类, 表示 吃 。如果当前的话于前面的某些真的话冲突,那么这句话就是假话,问一共有多少句假话。
思路: 已知 ,一个环形的状态。因此类似于一个模 剩余系, 表示 与 同类别, 表示 吃 , 表示 被 吃。因此所有偏移量加减都在模 剩余系中进行,具体实现可以查看代码。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 5*1e4+100;
int n,m,fa[N],d[N];
int find(int x){
if(x == fa[x]) return x;
int root = find(fa[x]);
d[x] = (d[fa[x]]+d[x])%3;
return fa[x] = root;
}
int main()
{
scanf("%d%d",&n,&m);
int ans = 0;
rep(i,0,n) fa[i] = i, d[i] = 0;
rep(i,1,m){
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(x > n || y > n){
ans++;
continue;
}
if(x == y && op == 2){
ans++;
continue;
}
int fx = find(x), fy = find(y);
if(fx == fy){
if(op == 1){
if((d[x]-d[y]+3)%3!=0) ans++;
}
else{
if((d[x]-d[y]+3)%3!=1) ans++;
}
}
else{
if(op == 1){
fa[fx] = fy;
d[fx] = (d[y]-d[x]+3)%3;
}
else{
fa[fx] = fy;
d[fx] = (d[y]-d[x]+3+1)%3;
}
}
}
printf("%d\n",ans);
return 0;
}
关押罪犯(NOIP 2010 / CODEVS 1069):
题意: 现在有 名罪犯,编号分别为 。有 对罪犯之间存在仇恨关系,有具体的怨气值 。现在需要将 名罪犯安排到两所监狱中,如果有仇恨的两名罪犯在同一监狱,则会发生冲突,事件影响力为其对应的怨气值。问如果分配罪犯,才能使发生的冲突中最大的影响值最小。输出答案即可。
思路: 由于本题只需要求影响力最大的冲突,因此我们可以贪心地来解决这个问题,先按照怨气值从大到小将 对罪犯进行排序,然后建立一个带权并查集, 表示 与 是否在同一个监狱。 表示 与 在同一监狱, 表示 与 在不同监狱,因此所有的偏移量计算都在模 剩余系中进行,也可以转化为异或计算。
所以对于每一对罪犯,查看他们所属并查集,如果不在同一并查集则将两者合并,并将其偏移量设为 。如果在同一并查集,则查看二者的偏移量,若偏移量为 ,则冲突不可避免,输出答案即可。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 2*1e4+1000;
const int M = 1e5+100;
int n,m,fa[N],d[N];
struct Node{
int a,b,c;
bool operator < (Node x) const {
return c > x.c;
}
}t[M];
int find(int x){
if(x == fa[x]) return x;
int root = find(fa[x]);
d[x] = (d[x]+d[fa[x]]+2)%2;
return fa[x] = root;
}
int main()
{
int ans = 0;
scanf("%d%d",&n,&m);
rep(i,1,m) scanf("%d%d%d",&t[i].a,&t[i].b,&t[i].c);
sort(t+1,t+1+m);
rep(i,0,n) fa[i] = i, d[i] = 0;
rep(i,1,m){
int x = t[i].a, y = t[i].b;
int fx = find(x), fy = find(y);
if(fx != fy){
fa[fx] = fy;
d[fx] = (1+d[y]-d[x]+2)%2;
}
else{
int yt = (d[x]-d[y]+2)%2;
if(!yt){
ans = t[i].c;
break;
}
}
}
printf("%d\n",ans);
return 0;
}
Rochambeau(POJ 2912):
题意: 个人在玩剪刀石头布的游戏,其中有一个人是裁判,其余人随机被分到了三个阵营,即剪刀、石头、布。现在有 轮游戏, 表示 与 之间的关系,其中裁判可以自由变换阵营。问是否可以根据这 轮游戏,判断出谁是裁判,输出裁判是谁以及最早在哪一轮可以找到裁判。如果无法判断,则输出 ,如果游戏的情况不符合题意,则输出 。
思路: 一开始做这题的时候,的确有些懵,只能想到如何发现有人变换了阵营,但是不知道如何找到这个人,并且确定这种情况是唯一的。
所以我们可以发现直接考虑整个问题会非常困难,再加上此题数据范围很小,直接做的话复杂度肯定很少,太对不起这个数据范围了。因此我们考虑 做法,即枚举每个人为裁判。枚举 为裁判时,如果 恰好为裁判,则并查集合并时不会发生矛盾。我们统计有多少个人为裁判时,并查集合并会发生矛盾,记人数为 ,并且统计每个人为裁判时,发生矛盾的轮数 。
现在我们来考虑输出答案的所有情况。
如果
,则可以唯一确定裁判,并且最早发现该裁判的轮数为
。
如果
,则输出
。
其余情况则为
。
至于带权并查集的合并类似于食物链问题,是个模 剩余系中的加减问题。
ps:读输入的时候1<3,如果用字符串读,不要只读一个数字… 推荐读取方式如下。
scanf("%d%c%d")
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 1000;
const int M = 2000+100;
int n,m,fa[N],d[N],vis[N],error[N],cnt;
int find(int x){
if(x == fa[x]) return x;
int root = find(fa[x]);
d[x] = (d[x]+d[fa[x]]+3)%3;
return fa[x] = root;
}
struct Query{
int x,y,cc;
}q[M];
int main()
{
while(~scanf("%d%d",&n,&m))
{
memset(vis,0,sizeof vis);
memset(error,0,sizeof error);
cnt = 0;
rep(i,1,m){
int x,y; char cc;
scanf("%d%c%d",&x,&cc,&y);
q[i].x = x, q[i].y = y;
if(cc == '=') q[i].cc = 0;
else if(cc == '<') q[i].cc = 1;
else q[i].cc = 2;
}
rep(i,0,n-1){
rep(k,0,n) fa[k] = k, d[k] = 0;
rep(j,1,m){
int x = q[j].x, y = q[j].y, cc = q[j].cc;
if(x == i || y == i) continue;
int fx = find(x), fy = find(y);
if(fx != fy){
fa[fx] = fy;
d[fx] = (d[y]-d[x]+cc+3)%3;
}
else{
int ttp = (d[x]-d[y]+3)%3;
if(ttp != cc){
cnt++; // 产生矛盾个数
error[i] = j;
vis[i] = 1;
break;
}
}
}
}
// impossible
// not determine
// determine
if(cnt == (n-1)) { //determine
int ans = 0, hm;
rep(i,0,n-1){
ans = max(ans,error[i]);
if(!vis[i]) hm = i;
}
printf("Player %d can be determined to be the judge after %d lines\n",hm,ans);
}
else if(cnt == n){ // impossible
printf("Impossible\n");
}
else{ // not determine
printf("Can not determine\n");
}
}
return 0;
}
True Liars(POJ 1417):
题意: 给出 个天使, 个魔鬼,一共有 个问题。每个问题的格式为 表示问 是否为天使,如果 为天使,则会说真话,如果 为魔鬼,则会说假话。问根据这 个问题,是否可以确定哪些人为天使,如果可以确定,按编号大小输出天使编号。
思路: 遇到这样的
问题,显然我们需要对于可能出现的情况进行模拟。
假如
为天使,
为天使,则回答
。
假如
为天使,
为魔鬼,则回答
。
假如
为魔鬼,
为天使,则回答
。
假如
为魔鬼,
为魔鬼,则回答
。
可以发现,如果回答是 ,则 与 属于同一类。如果回答 ,则 与 类别相反。因此一个模 剩余系的带权并查集合并就可以维护所有人之间的关系。
因此不难发现,处理完 个问题之后,我们会拥有若干个并查集,每个并查集中都会分为两批人,两批人类别不同。
因此问题变成了,从这若干个并查集中随机选一部分,使得最后选中的人数恰好为 ,并且这种选择方式唯一,则我们可以确定哪些人为天使。因此本题就变成了一个类似背包的问题, 表示前 个并查集选取人数为 一共有多少种选择方案, 表示 由 更新而来。到此,本题即可顺利解决。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <map>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 700;
int n,p1,p2,fa[N],d[N],base[N][2],tot,dp[N][600],pre[N][600],vis[N];
map<int,int> mp;
int find(int x)
{
if(x == fa[x]) return x;
int root = find(fa[x]);
d[x] = (d[x]+d[fa[x]]+2)%2;
return fa[x] = root;
}
void solve()
{
memset(base,0,sizeof base);
memset(vis,0,sizeof vis);
mp.clear();
tot = 0;
rep(i,1,p1+p2){
fa[i] = find(i);
if(mp.find(fa[i]) == mp.end()) mp[fa[i]] = ++tot;
int pos = mp[fa[i]];
base[pos][d[i]]++; //0: 相同 1:不同
}
memset(dp,0,sizeof dp);
memset(pre,0,sizeof pre);
dp[1][base[1][1]]++;
dp[1][base[1][0]]++;
rep(i,2,tot){
int minn = min(base[i][0],base[i][1]);
rep(j,minn,p1){
if(j >= base[i][0] && dp[i-1][j-base[i][0]] != 0)
dp[i][j] += dp[i-1][j-base[i][0]], pre[i][j] = j-base[i][0];
if(j >= base[i][1] && dp[i-1][j-base[i][1]] != 0)
dp[i][j] += dp[i-1][j-base[i][1]], pre[i][j] = j-base[i][1];
}
}
if(dp[tot][p1] != 1) printf("no\n");
else{
int xx = tot, yy = p1;
while(xx > 1){
if(pre[xx][yy] == yy-base[xx][0]) vis[xx] = 0, yy -= base[xx][0];
else if(pre[xx][yy] == yy-base[xx][1]) vis[xx] = 1, yy -= base[xx][1];
xx--;
}
if(xx == 1){
if(base[1][0] == yy) vis[1] = 0;
else vis[1] = 1;
}
rep(i,1,p1+p2){
int pos = mp[fa[i]];
int dd = d[i];
if(vis[pos] == 1 && dd == 1) printf("%d\n",i);
else if(vis[pos] == 0 && dd == 0) printf("%d\n",i);
}
printf("end\n");
}
}
int main()
{
while(~scanf("%d%d%d",&n,&p1,&p2))
{
if((n+p1+p2) == 0) break;
rep(i,0,p1+p2) fa[i] = i, d[i] = 0;
rep(i,1,n){
int x,y; char op[10];
scanf("%d%d",&x,&y);
scanf("%s",op);
int fx = find(x), fy = find(y);
// LOG2("x",x,"y",y);
// LOG2("fx",fx,"fy",fy);
if(fx != fy){
fa[fx] = fy;
if(op[0] == 'n') d[fx] = (2+1+d[y]-d[x])%2;
else d[fx] = (d[y]-d[x]+2)%2;
}
}
solve();
}
return 0;
}