2018 Multi-University Training Contest 6 Werewolf(Tarjan+染色+基环外向树)

本文深入探讨了流行桌游“狼人杀”的玩家角色判断逻辑,通过建立数学模型,解析了村民与狼人在辩论过程中的策略选择,揭示了游戏背后的逻辑推理与博弈论原理。

 

Problem Description

"The Werewolves" is a popular card game among young people.In the basic game, there are 2 different groups: the werewolves and the villagers.

Each player will debate a player they think is a werewolf or not.

Their words are like "Player x is a werewolf." or "Player x is a villager.".

What we know is :

1. Villager won't lie.

2. Werewolf may lie.

Of cause we only consider those situations which obey the two rules above.

It is guaranteed that input data exist at least one situation which obey the two rules above.

Now we can judge every player into 3 types :

1. A player which can only be villager among all situations,

2. A player which can only be werewolf among all situations.

3. A player which can be villager among some situations, while can be werewolf in others situations.

You just need to print out the number of type-1 players and the number of type-2 players.

No player will talk about himself.

Input

The first line of the input gives the number of test cases T.Then T test cases follow.

The first line of each test case contains an integer N,indicating the number of players.

Then follows N lines,i-th line contains an integer x and a string S,indicating the i-th players tell you,"Player x is a S."

limits:

1≤T≤10

1≤N≤100,000

1≤x≤N

S∈ {"villager"."werewolf"}

Output

For each test case,print the number of type-1 players and the number of type-2 players in one line, separated by white space.

Sample Input

1 2 2 werewolf 1 werewolf

Sample Output

0 0

菜鸡题解:

首先由于所有人都是werewolf的情况是必定合法的,所以必定是villager的人不存在的。进而问题转化为求必定是werewolf的人有多少。然后在纸上瞎画画,可以发现这样一个样例 A说B为villager,B说A为werewolf。显然这样的话A必为werewolf。然后再瞎推推发现这个样例可以扩展为 A说B为villager,B说C为villager,C说D为......N-1说N为villager,N说A为werewolf,结果还是A必为werewolf。然后再瞎想想发现A为werewolf的话所有直接或间接指定A为villager的人都是werewolf。

将以上思路整理一下之后可以发现可以这样做:

先把所有A说B为villager的情况建成A->B的有向边,从而得到一张图V(可能是多个不连通的图),并再建一张由B->A组成的反向图V`。另外我们发现如果图V中有环的话环上的点必定是不确定的,所以跑一遍Tarjan把环点都删掉。然后只需把每条werewolf边加入图中,如果werewolf边A->B连接的两个点在同一个图中(可以用染色法预处理来判断是否同一图)则B必为werewolf,然后通过反向图V`来找到有多少个点直接或间接指定B为villager(记得算上B点自己)然后求和就是结果。

个人菜鸡代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

const int MAXN = 100005;

struct EdgeW{//存werewolf边 
	int from,to;
	EdgeW(int a,int b):from(a),to(b){}
};

vector<struct EdgeW> W;

struct Edge{//存villager正向边 
	int to,_next;
}E[MAXN];

struct EdgeR{//存villager反向边 
	int to,_next;
}ER[MAXN];

int head[MAXN],tot;
int headr[MAXN],totr;

inline void Add(int from,int to){
	E[++tot]._next = head[from];
	E[tot].to = to;
	head[from] = tot;
	ER[++totr]._next = headr[to];
	ER[totr].to = from;
	headr[to] = totr;
}

bool is_instack[MAXN];
int sstack[MAXN],top;
int DFN[MAXN];
int LOW[MAXN];
int timee;
int Belong[MAXN];
int N,cnt;
bool Del[MAXN];//记录强联通分量是否删除 

void Tarjan(int x){
    DFN[x] = LOW[x] = ++timee;
    is_instack[x] = true;
    sstack[++top] = x;
    for(int i=head[x] ; i ; i=E[i]._next){
        if(!DFN[E[i].to]){
            Tarjan(E[i].to);
            if(LOW[E[i].to]<LOW[x]){
                LOW[x] = LOW[E[i].to];
            }
        }
        else if(is_instack[E[i].to]){
             LOW[x] = min(LOW[x],DFN[E[i].to]);
        }
    }
    if(DFN[x] == LOW[x]){
        ++cnt;
        int mid;
        int t = 0;//记录该强联通分量包含的点数 
        do{
            mid = sstack[top--];
            is_instack[mid] = false;
            Belong[mid] = cnt;
            ++t;
        }while(mid != x);
        if(t > 1)Del[cnt] = true;//如果点数大于1删除 
    }
}

int color[MAXN];

void putColor(int x){
	for(int i=head[x] ; i ; i=E[i]._next){
		if(color[E[i].to])continue;
		else {
			color[E[i].to] = color[x];
			putColor(E[i].to);
		}
	}
	for(int i=headr[x] ; i ; i=ER[i]._next){
		if(color[ER[i].to])continue;
		else {
			color[ER[i].to] = color[x];
			putColor(ER[i].to);
		}
	}
}

int DFS(int x){//用来查直接或间接说werewolf点为villager的点的数量 
	int sum = 1;
	for(int i=headr[x] ; i ; i=ER[i]._next){
		sum += DFS(ER[i].to);
	}
	return sum;
}

inline void init(){
    memset(head,0,sizeof head);
    memset(headr,0,sizeof headr);
    memset(DFN,0,sizeof(DFN));
    memset(LOW,0,sizeof(LOW));
    memset(is_instack,false,sizeof(is_instack));
    memset(Del,false,sizeof Del);
    timee = cnt = top = tot = totr = 0;
    memset(color,0,sizeof color);
}

int main(){
	
	int T;
	char s[25];
	scanf("%d",&T);
	while(T--){
		scanf("%d",&N);
		init();
		W.clear();
		if(N == 1)printf("0 0\n");
		else {
			int a;
			for(int i=1 ; i<=N ; ++i){
				scanf("%d %s",&a,s);
				if(s[0] == 'w')W.push_back(EdgeW(i,a));
				else Add(i,a);
			}
			for(int i=1 ; i<=N ; ++i)if(!DFN[i])Tarjan(i);
			//for(int i=1 ; i<=N ; ++i)printf("%d ",Belong[i]);
			for(int i=1 ; i<=N ; ++i)if(!color[i]){
				color[i] = i;
				putColor(i);
			}
			//for(int i=1 ; i<=N ; ++i)printf("%d ",color[i]);
			int sum = 0;
			for(int i=0 ; i<W.size() ; ++i){
				int from = W[i].from;
				int to = W[i].to;
				if(Del[Belong[to]] || color[from] != color[to])continue;
				else sum += DFS(to);
			}
			printf("0 %d\n",sum);
		}
	}
	
	return 0;
}

大佬神仙代码(其实速度差不多):

#include <bits/stdc++.h>

using namespace std;

typedef double db;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;

#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define pw(x) (1ll << (x))
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define rep(i,l,r) for(int i=(l);i<(r);i++)
#define per(i,l,r) for(int i=(r)-1;i>=(l);i--)
#define dd(x) cout << #x << " = " << (x) << ", "
#define de(x) cout << #x << " = " << (x) << "\n"
#define endl "\n"

//-----
const int N=100005;
pii data[N];
int ans[N],flag[N];
void dfs(int a)
{
    flag[a]=1;
    if(data[a].se)
    {
        if(!flag[data[a].fi])
        {
            dfs(data[a].fi);
            if(ans[data[a].fi]==-1)ans[a]=-1;
            else if(a==ans[data[a].fi])ans[a]=-1;
            else ans[a]=ans[data[a].fi];
        }
        else if(!ans[data[a].fi])ans[a]=N;
        else
        {
            ans[a]=ans[data[a].fi];
            if(ans[a]==a)ans[a]=-1;
        }
    }
    else ans[a]=data[a].fi;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
        int T;cin>>T;
        while(T--)
        {
            int n;cin>>n;
            rep(i,1,n+1)ans[i]=0,flag[i]=0;
            rep(i,1,n+1)
            {
                int x;string s;cin>>x>>s;
                if(s=="villager")data[i]=mp(x,1);
                else data[i]=mp(x,0);
            }
            rep(i,1,n+1)if(!ans[i])dfs(i);
            int res=0;
            rep(i,1,n+1)res+=ans[i]==-1;
            //rep(i,1,n+1)cout<<ans[i]<<endl;
            cout<<"0 "<<res<<endl;
        }
    return 0;
}

 

用户表: Users +-------------+---------+ | Column Name | Type | +-------------+---------+ | user_id | int | | user_name | varchar | +-------------+---------+ user_id 是该表的主键(具有唯一值的列)。 该表中的每行包括用户 ID 和用户名。 注册表: Register +-------------+---------+ | Column Name | Type | +-------------+---------+ | contest_id | int | | user_id | int | +-------------+---------+ (contest_id, user_id) 是该表的主键(具有唯一值的列的组合)。 该表中的每行包含用户的 ID 和他们注册的赛事。 编写解决方案统计出各赛事的用户注册百分率,保留两位小数。 返回的结果表按 percentage 的 降序 排序,若相同则按 contest_id 的 升序 排序。 返回结果如下示例所示。 示例 1: 输入: Users 表: +---------+-----------+ | user_id | user_name | +---------+-----------+ | 6 | Alice | | 2 | Bob | | 7 | Alex | +---------+-----------+ Register 表: +------------+---------+ | contest_id | user_id | +------------+---------+ | 215 | 6 | | 209 | 2 | | 208 | 2 | | 210 | 6 | | 208 | 6 | | 209 | 7 | | 209 | 6 | | 215 | 7 | | 208 | 7 | | 210 | 2 | | 207 | 2 | | 210 | 7 | +------------+---------+ 输出: +------------+------------+ | contest_id | percentage | +------------+------------+ | 208 | 100.0 | | 209 | 100.0 | | 210 | 100.0 | | 215 | 66.67 | | 207 | 33.33 | +------------+------------+ 解释: 所有用户都注册了 208、209 和 210 赛事,因此这些赛事的注册率为 100% ,我们按 contest_id 的降序排序加入结果表中。 Alice 和 Alex 注册了 215 赛事,注册率为 ((2/3) * 100) = 66.67% Bob 注册了 207 赛事,注册率为 ((1/3) * 100) = 33.33%
最新发布
03-18
<think>好的,我需要解决这个问题。首先,我得仔细理解题目要求。题目是要统计每个赛事的用户注册百分比,保留两位小数,然后按百分比降序,如果相同的话按contest_id升序排列。给出的示例可以帮助我验证思路是否正确。 首先,我需要确定如何计算每个赛事的百分比。百分比应该是该赛事注册的用户数除以总用户数,然后乘以100,保留两位小数。总用户数来自Users表,因为每个用户可能注册多个赛事,但总用户数是固定的,不管他们注册了多少赛事。比如示例中Users表有3个用户,所以每个赛事的注册用户数除以3,再乘以100得到百分比。 那首先,我需要从Users表中获取总用户数。可以用COUNT(DISTINCT user_id)来统计总人数。然后,对于Register表中的每个contest_id,计算该赛事的注册用户数,再除以总用户数,得到百分比。 接下来是SQL的写法。可能需要子查询或者JOIN来结合这两个表的数据。不过,实际上Register表中每个contest_id对应的用户数可以用GROUP BY contest_id来计算每个赛事的用户数量。然后将这个数量除以总用户数,乘以100,然后四舍五入到两位小数。 那步骤大概是: 1. 计算总用户数:SELECT COUNT(*) FROM Users; 2. 对Register表按contest_id分组,统计每个分组的用户数; 3. 将每个分组的用户数除以总用户数,得到百分比; 4. 按百分比降序和contest_id升序排序。 需要注意的是,总用户数可能需要作为子查询或者变量来处理。例如,在SELECT语句中可能需要用到子查询来获取总用户数。 比如,可以这样写: SELECT contest_id, ROUND( (COUNT(user_id) / (SELECT COUNT(*) FROM Users)) * 100, 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id ASC; 但这里有个问题,是否需要考虑重复的user_id在同一个赛事中的情况?不过根据题目,Register表的主键是(contest_id, user_id),所以每个用户在一个赛事中只能注册一次。因此,每个分组的COUNT(user_id)就是该赛事的不同用户数。而总用户数是Users表中的总人数,不管他们是否注册了任何赛事。例如,如果有用户没有注册任何赛事,总用户数仍然包括他们,但该用户不会被统计在任何赛事的注册数中。 比如示例中的Users表有3个用户,每个赛事的注册用户数是该赛事有多少不同的user_id记录,比如contest_id 207在Register表中只有user_id 2,所以注册数是1,总用户数是3,所以百分比是33.33%。 那上述的SQL是否正确? 测试一下示例的情况。示例中的Register表对于contest_id=208,有三个不同的user_id吗?根据输入: Register表中contest_id=208的有user_id=2、6、7?比如输入中的Register表: 208的条目有: 208 | 2 208 | 6 208 | 7 所以在contest_id=208的分组中,COUNT(user_id)是3。总用户数是3,所以3/3=1,即100%。和示例的输出一致。 那上述的SQL应该能正确计算。 但是,当使用COUNT(user_id)的时候,如果某个用户没有注册任何赛事,他们不会被统计进来,但总用户数仍然会包含他们。例如,如果Users表中有一个用户没有在Register表中出现,那总用户数是正确的,而每个赛事的百分比是注册用户数/总用户数,包括这个未被注册的用户。这种情况下,计算是正确的。 那现在,这个SQL是否正确? 是的。因为总用户数是来自Users表的总数,而每个赛事的注册用户数是该赛事中的用户数,所以除法是正确的。 然后,ROUND函数保留两位小数。比如在示例中的215赛事,注册用户数是2(用户6和7),总用户数是3,所以2/3=0.666666…,乘以100是66.666…,四舍五入到两位小数是66.67,符合示例。 那这个时候,这个SQL应该是正确的。然后需要将结果按percentage降序,contest_id升序排列。 但是示例的输出中,208、209、210都是100%,按contest_id降序排列吗?例如,输出中的顺序是208、209、210、215、207。但是contest_id的值是208,209,210的数值是递增的,所以按contest_id升序排列的话,这三个的排列顺序应该是208、209、210。这与示例的输出中的顺序一致。所以ORDER BY子句中的第二部分是contest_id ASC,这正确。 所以整个SQL应该是: SELECT contest_id, ROUND( (COUNT(*) * 100.0) / (SELECT COUNT(*) FROM Users), 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id ASC; 这里需要注意,乘以100.0而不是100,以确保得到浮点数的结果,避免整数除法的问题。例如,在MySQL中,如果分母是整数,那么除法会得到整数部分。所以需要使用浮点数运算。 比如,在示例中的207赛事,注册用户数是1,总用户数是3。1/3是0.333…,乘以100是33.333…。保留两位小数是33.33。如果使用整数运算,可能得到33.00或者错误的数值。所以必须确保计算时使用浮点数。 因此,在SQL中,将分子或分母中的一个转换为浮点数,例如乘以100.0,或者将COUNT(*)转换为小数。 所以,正确的写法应该是: ROUND( (COUNT(user_id) * 100.0) / total_users, 2 ) 其中total_users是用户总数。或者,在MySQL中,可能不需要,但为了保险,乘以100.0是正确的。 现在,在编写这个SQL时,是否应该使用COUNT(*),或者COUNT(user_id)?在GROUP BY contest_id之后,每个分组的行数等于该赛事中的注册用户数目,因为每个用户只能注册一次同一赛事。所以COUNT(*)和COUNT(user_id)的结果是一样的。所以两种写法都可以。 因此,最终的SQL语句应该是: SELECT contest_id, ROUND( (COUNT(*) * 100.0) / (SELECT COUNT(*) FROM Users), 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id; 这样应该就能得到正确的结果。然后,按照示例的输出,验证是否正确。 测试示例中的输入: Users表有3个用户。Register表中的数据: contest_id 215有用户6和7,即COUNT(*)为2。2*100/3=66.666...,四舍五入到两位小数是66.67。符合示例的输出。 contest_id 207有一个用户,计算1*100/3=33.33,正确。 其他三个赛事208、209、210的注册用户数都是3,所以3/3=100%正确。 所以SQL的输出应该与示例一致。 那这个应该就是正确的解决方案了。</think>要解决这个问题,我们需要统计每个赛事的用户注册百分比,并按特定规则排序结果。以下是详细的步骤说明和解决方案: ### 方法思路 1. **计算总用户数**:从 `Users` 表中获取所有用户的总数。 2. **统计各赛事注册用户数**:在 `Register` 表中按 `contest_id` 分组,统计每个赛事的注册用户数。 3. **计算百分比**:将每个赛事的注册用户数除以总用户数,乘以 100 并保留两位小数。 4. **排序结果**:按百分比降序排序,若百分比相同则按赛事 ID 升序排序。 ### 解决代码 ```sql SELECT contest_id, ROUND( (COUNT(*) * 100.0) / (SELECT COUNT(*) FROM Users), 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id ASC; ``` ### 代码解释 1. **总用户数计算**:通过子查询 `(SELECT COUNT(*) FROM Users)` 获取 `Users` 表中的总用户数。 2. **分组统计注册用户数**:使用 `GROUP BY contest_id` 对 `Register` 表按赛事分组,`COUNT(*)` 统计每个赛事的注册用户数。 3. **计算百分比**:将每个分组的注册用户数乘以 100.0(确保浮点数运算),除以总用户数,并用 `ROUND` 函数保留两位小数。 4. **排序规则**:结果首先按百分比降序 (`percentage DESC`) 排列,若百分比相同则按赛事 ID 升序 (`contest_id ASC`) 排列。 这种方法确保准确计算各赛事的注册率,并按要求排序结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值