PKU演習問メモ(8/24)

センスなくて死にたい。

No. 問題名 問題の種類および解法 難易度
2259 Team Queue データ構造(キュー) ★★★☆☆
2258 The Settlers of Catan グラフ・深さ優先探索 ★★☆☆☆
2230 Watchcow グラフ・深さ優先探索 ★★★☆☆
2208 Pyramids 幾何・四面体の体積 ★★★☆☆
2121 Inglish-Number Translator 実装・文字列 ★★★☆☆

2259 Team Queue

問題概要

"Team Queue"を以下のようなキューとして定義する。

push
同じチームのデータがキューに入っているときはそれらの直後に、そうでない場合は最後尾にデータを追加する。
pop
先頭からデータを取り出す。


このとき、与えられたクエリを処理せよ。
チーム数は1000以下、クエリの数は200000以下である。

解法

クエリの数が大きく、O(1)でpop,pushの操作ができないといけない(と問題文にある)
チームの数が少ないので、チーム数分キューを作り、チーム全体を要素にする親玉のキューを作れる。
これにより各操作がO(1)で行える。

ソースコード
int team[1000000];

int main()
{
	int n,cs=0;
	while(scanf("%d",&n),n)
	{
		printf("Scenario #%d\n",++cs);
		rep(i,n)
		{
			int m,k; scanf("%d",&m);
			rep(j,m)scanf("%d",&k),team[k]=i;
		}
		vector<queue<int> > Q(n);
		queue<int> QQ;
		
		char op[10]; int t,tt;
		while(scanf("%s",op),op[0]!='S')
		{
			if(op[0]=='D')
			{
				tt=QQ.front(); t=Q[tt].front(); Q[tt].pop();
				printf("%d\n",t);
				if(Q[tt].empty())QQ.pop();
				
				continue;
			}
			scanf("%d",&t);
			tt=team[t];
			if(Q[tt].empty())QQ.push(tt);
			Q[tt].push(t);
		}
		puts("");
	}
	return 0;
}

2258 The Settlers of Catan

問題概要

"カタンの開拓者たち"のゲームにおける道の長さを求めよ。
(道の長さはカタンにおける定義にしたがう)
ゲームの状態は、都市の数n個および、m本のそれらを結ぶ道の情報により与えられる。
各都市の次数は3以下である。


また、n,m≦25を満たす。

解法

mが少ないので、使ったエッジにフラグを立てながら深さ優先で全探索すればよい。

ソースコード
int n,m;
pi e[60];
int top[60],rv[60],ans;
bool v[60];

void dfs(int c,int p,int s)
{
	ans=max(ans,s);
	for(int i=top[c];i;i=e[i].second)if(!v[i]&&p!=e[i].first)
	{
		v[i]=1; v[rv[i]]=1;
		dfs(e[i].first,c,s+1);
		v[i]=0; v[rv[i]]=0;
	}
}
int main()
{
	while(scanf("%d%d",&n,&m),n)
	{
		rep(i,60)e[i]=mp(0,0);
		rep(i,60)v[i]=top[i]=0;
		
		int ei=0;
		rep(i,m)
		{
			int a,b,t; scanf("%d%d",&a,&b);
			t=top[a]; top[a]=++ei; e[ei]=mp(b,t);
			t=top[b]; top[b]=++ei; e[ei]=mp(a,t);
			
			rv[ei-1]=ei; rv[ei]=ei-1;
		}
		ans=0;
		rep(i,n)
		{
			rep(j,n)v[j]=0; dfs(i,i,0);
		}
		printf("%d\n",ans);
	}
	return 0;
}

2230 Watchcow

問題概要

N個の牧場をM本の双方向に通行可能な道路が結んでいる。
今、1番の牧場を出発し、全ての道路を"違う向きに丁度1度ずつ(合計丁度2回)"通り、1番の道路に戻ってきたい。


そのようなルートを一つ出力せよ。
N≦10000,M≦50000を満たす。

解法

1番のノードから、一度使った辺は二度通らないように深さ優先探索をする。
行き止まりまで行ったら、自分を出力して、再帰関数は呼び出し元へと帰るが、そこで更にそのノードを出力する(更にその呼び出し元で……)とすると、丁度問題文で求められている経路を出力することができる。


辺のリストは以前に使った下のようなデータ構造が適しているように思われる。
(疎なグラフを省メモリに表すことができ、逆辺がO(1)で探せる)

ソースコード
int n,m;
struct S{
	int t,id;
};
S node[10001];
pi e[100001]; int rv[100001];
bool v[100000];
int ans[200000],l;

void dfs(int c,int p)
{
	ans[l++]=c;
	for(int ei=node[c].id;ei;ei=e[ei].second)
	if(e[ei].first!=p)
	{
		if(!v[ei])
		{
			v[ei]=1; v[rv[ei]]=1;
			dfs(e[ei].first,c);
			ans[l++]=c; 
		}
	}
	if(ans[l-1]!=c)ans[l++]=c; 
}

int main()
{
	scanf("%d%d",&n,&m);
	int ei=0;
	rep(i,m)
	{
		int a,b,tmp; scanf("%d%d",&a,&b);
		tmp=node[a].id;
		node[a].id=++ei; e[ei]=mp(b,tmp);
		rv[ei]=ei+1;
		
		tmp=node[b].id;
		node[b].id=++ei; e[ei]=mp(a,tmp);
		rv[ei]=ei-1;
	}
	dfs(1,1);
	rep(i,l)printf("%d\n",ans[i]);
	
	return 0;
}

2208

問題概要

四面体ABCDの四辺AB,AC,AD,BC,BD,CDの長さが与えられる。
このとき、四面体の体積を求めよ。

解法

辺の長さから四面体の体積を求める公式があるらしい。(5次の行列式を使うので手計算には向かない)
http://www004.upp.so-net.ne.jp/s_honma/figure/tetrahedronvolume.htm

ソースコード

spaghetti source行列式のコードを使用(http://www.prefield.com/algorithm/math/matrix.html

int main()
{
	int a,b,c,p,q,r;
	scanf("%d%d%d%d%d%d",&a,&b,&c,&p,&q,&r);
	
	matrix M(5,array(5));
	rep(i,4)M[4][i]=M[i][4]=1;
	M[0][1]=M[1][0]=p*p; M[0][2]=M[2][0]=q*q; M[1][2]=M[2][1]=r*r;
	M[3][0]=M[0][3]=a*a; M[3][1]=M[1][3]=b*b; M[3][2]=M[2][3]=c*c;
	
	double ans=sqrt(det(M)/288);
	printf("%.4f\n",ans);
	
	return 0;
}

2121 Inglish-Number Translator

問題概要

"Inglish-Number"を数字に変換せよ。
(英語の数詞を使った数の表記だが、hundred,thousand,millionの前のoneが省略されない)

解法

構文解析に近いようなこと(もっと簡単)をする。
oneがあるので楽。ソース参照。

ソースコード
char* s1[]={"zero","one","two","three","four","five","six","seven","eight","nine","ten","eleven",
	"twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen","twenty"};
char* s2[]={"thirty","forty","fifty","sixty","seventy","eighty","ninety","hundred"};

map<string,int> conv;
vs num;

int main()
{
	rep(i,21)conv[s1[i]]=i;
	rep(i,8)conv[s2[i]]=(i+3)*10;
	
	conv["negative"]=-1;
	conv["thousand"]=1000; conv["million"]=1000000;
	
	string in;
	while(getline(cin,in),in!="")
	{
		num.clear();
		stringstream ss(in);
		
		while(ss>>in)num.pb(in);
		
		int ans=0,tmp=0,sgn=1;
		fr(i,num)
		{
			if(conv[*i]==100)tmp*=100;
			else if(conv[*i]>100)ans+=tmp*conv[*i],tmp=0;
			else
			{
				if(conv[*i]<0)sgn=-1;
				else tmp+=conv[*i];
			}
		}
		ans+=tmp;
		
		cout<<ans*sgn<<endl;
	}
	return 0;
}