Lilliput Steps

小さな一歩から着実に. 数学やプログラミングのことを書きます.

JOI春合宿 2011-day3 report

問題文 : 報告

解法 :

まず, 問題文の例を解析する.
f:id:kagamiz:20130216084643p:plain

報告先でサイクルになっている頂点達について, サイクル内の頂点v に仕事の報告が来れば, 必ず他の頂点でも仕事の報告が来ることがわかるので, 閉路を潰す.

次に, 閉路を潰して出来たグラフの逆辺からなるグラフを考えると, これが木構造になることがわかる. (大きなデータの場合は森になりそうなことも考察できる)

f:id:kagamiz:20130216084648p:plain

ここまでわかれば, 根からeuler-tourに似たような番号付けをすることで(ただし, 辺ではなく頂点について番号付けをする) BIT でうまく加算や総和計算をすることが出来る.

f:id:kagamiz:20130216084658p:plain

コード :

#include <cstdio>
#include <vector>
#include <cstring>

#define MAX_N (100100)

using namespace std;

int Vid[2 * MAX_N];
int bit[2 * MAX_N];
int gr[MAX_N];
bool vis[MAX_N];
vector<int> G[MAX_N], rev[MAX_N], cmp[MAX_N], ord;

void add(int n){
	while (n < 2 * MAX_N){
		bit[n] += 1;
		n += n & -n;
	}
}

int sum(int pos)
{
	int ret = 0;
	
	while (pos){
		ret += bit[pos];
		pos &= (pos - 1);
	}
	
	return (ret);
}

void dfs(int v)
{
	vis[v] = true;
	for (int i = 0; i < G[v].size(); i++)
		if (!vis[G[v][i]]) dfs(G[v][i]);
	ord.push_back(v);
}

void revdfs(int v, int k)
{
	vis[v] = true; gr[v] = k;
	for (int i = 0; i < rev[v].size(); i++)
		if (!vis[rev[v][i]]) revdfs(rev[v][i], k);
}

void number(int v, int &k)
{
	vis[v] = true; Vid[v * 2] = k++;
	
	for (int i = 0; i < cmp[v].size(); i++)
		if (!vis[cmp[v][i]]) number(cmp[v][i], k);
	
	Vid[v * 2 + 1] = k++;
}	


int main()
{
	int N;
	int to[MAX_N];
	
	scanf("%d", &N);
	
	for (int i = 0; i < N; i++){
		scanf("%d", to + i);
	}
	
	for (int i = 0; i < N; i++){
		G[i].push_back(--to[i]);
		rev[to[i]].push_back(i);
	}
	
	memset(vis, false, sizeof(vis));
	for (int i = 0; i < N; i++) if (!vis[i]) dfs(i);
	
	memset(vis, false, sizeof(vis));
	int k = 0;
	for (int i = N - 1; i >= 0; i--)
		if (!vis[ord[i]]) revdfs(ord[i], k++);
	
	for (int i = 0; i < N; i++) rev[i].clear();
	
	for (int i = 0; i < N; i++){
		if (gr[i] != gr[to[i]]){
			cmp[gr[to[i]]].push_back(gr[i]);
			rev[gr[i]].push_back(gr[to[i]]);
		}
	}
	
	k = 1;
	memset(vis, false, sizeof(vis));
	for (int i = 0; i < N; i++){
		if (!vis[gr[i]] && rev[gr[i]].size() == 0) number(gr[i], k);
	}
	
	for (int i = 0; i < N; i++){
		printf("%d\n", sum(Vid[gr[i] * 2 + 1]) - sum(Vid[gr[i] * 2] - 1));
		add(Vid[gr[i] * 2]);
	}
	
	return (0);
}