C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[32]: プログラムの練習問題をゆっくりと考えていきます


(過去ログ 105 を表示中)

[トピック内 37 記事 (1 - 20 表示)]  << 0 | 1 >>

■62667 / inTopicNo.1)  プログラムの練習問題をゆっくりと考えていきます
  
□投稿者/ 堀江伸一 (51回)-(2011/10/25(Tue) 18:06:34)

分類:[C/C++] 

2011/10/25(Tue) 18:35:16 編集(投稿者)

プログラムの練習問題を解いて勉強していくための質問スレとして立てました。

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0504
今回考えたいのはリンク先問題です。
カードの山をルールに従って取り除いていき、すべてのカードを2回以内の試行で取り除ける確率を求めるという問題です。


独力で解こうとしたもののどう考えても2回目の確率計算の方法を思いつきませんでした。

そこで答えのページを見たのですが、これがちょっと難しそう。
なんとなく全体の解き方の雰囲気はわかるので非常に難しそうという感じではありません。
そこで考え方をご指導もしくは解説いただけたらと考えます。


採点はGCCでコンパイルされジャッジデータで採点されます。
採点データはリンク先の問題5です。
http://www.ioi-jp.org/joi/2005/2006-yo-prob_and_sol/index.html

今回は二つご指導いただきたいことがあります。
質問1 多倍長計算はGCC標準のものはあるでしょうか、自前で用意する必要があるでしょうか?
質問2 2回目までに成功する確率が 1/n + 1/n (1 + 1/2 + 1/3 + … + 1/(n - 1)) であることの証明の部分が少し難しいのですがわかりやすい説明をいただけたらと思います?

特にn回目に1が出るのくだりが難しく、k = 1 の場合を考えれば十分につながる理由がよくわかってない状態です。
引用返信 編集キー/
■62670 / inTopicNo.2)  Re[1]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ shu (1054回)-(2011/10/25(Tue) 22:54:31)
No62667 (堀江伸一 さん) に返信

> 今回は二つご指導いただきたいことがあります。
> 質問1 多倍長計算はGCC標準のものはあるでしょうか、自前で用意する必要があるでしょうか?
ないと思います。あったとしても問題を解くのが目的なら自前で用意するのが普通かと思います。


> 質問2 2回目までに成功する確率が 1/n + 1/n (1 + 1/2 + 1/3 + … + 1/(n - 1)) であることの証明の部分が少し難しいのですがわかりやすい説明をいただけたらと思います?
>
> 特にn回目に1が出るのくだりが難しく、k = 1 の場合を考えれば十分につながる理由がよくわかってない状態です。

何個の山でもなくなる確率が1/nということでしょう。
そうするとi個山がなくなったとすると残りの山にある数字はn-i個なので全てなくすには1/n-iの確率になり
1回目の1/nとかけて1/n * 1/n-i になるということでしょう。それを全部足せば提示されている確率になります。

k=1の場合を考えれば十分なのはその前で1回目の成功確率でkに依存していないことを示したのと同様に考えることが出来るからだと思います。

引用返信 編集キー/
■62675 / inTopicNo.3)  Re[2]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (52回)-(2011/10/26(Wed) 17:23:57)
2011/10/26(Wed) 17:26:21 編集(投稿者)

解説ありがとうございます。
まだ難しいのでよくわかりません。
模範解答URL→http://www.ioi-jp.org/joi/2005/2006-yo-prob_and_sol/2006-yo-t5-review.html


模範回答のうちで私に理解できた部分を書きだしてみます。

1番の山から初めた場合カードが 2,2,3、、の場合2が2枚減って2の山が二つ減る、2,3,2,3、、、のような並びでも2が2枚減って2の山が2枚減る。
この例のようにどの山もn番のカードが1枚出たらn番目の山が一つ減る1対1対応なので1番以外の山で試行が終わることはない。
試行は1番の山だけ1枚減った状態から始まるので、一回目の失敗は必ず1番の山をとろうとする時だけとなる。

2回目からの試行では、上記と同じ議論で2回目の試行で最初にとった山の番号が最後のカードの番号となる必要がある。


疑問点
問題は「k = 1 の場合を考えれば十分である」の部分。
なぜk=mに一般化できるのかという部分です。
K=1からkを増やす帰納法や組み合わせを使う方法などいくつか試してみましたがうまくいきませんでした。


k=mの試行では、2回目の試行で残っているカードの枚数をuとし、再開した山の番号iと残っている各カードの枚数をKi(i=1,,,n)とすると
2回目の試行で終了できる確率は
ki(u-1)!/(u!)=ki/u
最後のカード番号がiになる確率はki/uなので(ki/u)^2
これが全てのkiで成り立つので2回目の試行で終わる確率は
A=(k1^2+,,,kn^2)/(u^2)?
Aに一回目の試行がb枚目で終わる確率Bをかけて
BAのすべての組みわせを足す。
なんて式を考えたりして悩んでいます。
一体どのような計算や証明がありうるのでしょうか?
引用返信 編集キー/
■62676 / inTopicNo.4)  Re[2]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (53回)-(2011/10/26(Wed) 17:26:31)
2011/10/26(Wed) 17:29:34 編集(投稿者)
2011/10/26(Wed) 17:29:31 編集(投稿者)

この投稿は送信ミスなので消しました。
引用返信 編集キー/
■62678 / inTopicNo.5)  Re[3]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ ザビエル (1回)-(2011/10/26(Wed) 19:26:06)
> 疑問点
> 問題は「k = 1 の場合を考えれば十分である」の部分。
> なぜk=mに一般化できるのかという部分です。

どこにk=mの説明が載っているのでしょうか。
おそらくこのケースではm = 1なので、それと混同しているのだと思いますが、
kもmも別のパラメータなので、kとmが関連しているわけがありません。

それでは、なぜ「k = 1 の場合を考えれば十分である」のか。
解説を読む限り、k は確率に関与しないので、kは条件を満たせば何でもいいはずです。
ただし、k = 1とおけば、最後の式のように整理され、計算がしやすいからだと読み取れましたが、いかが?
引用返信 編集キー/
■62679 / inTopicNo.6)  Re[3]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ shu (1055回)-(2011/10/27(Thu) 07:28:07)
2011/10/27(Thu) 07:34:36 編集(投稿者)

No62675 (堀江伸一 さん) に返信

> 疑問点
> 問題は「k = 1 の場合を考えれば十分である」の部分。
> なぜk=mに一般化できるのかという部分です。
感覚的に考えるとk-1枚目までの並び方というのは成功、失敗に影響せず、それぞれのカードの
最後がどう並んでいるかだけになるということになるのではないでしょうか。
1以外の山がなくなるということはその山の数がk枚めくられたということだけでk-1枚までがどのような順番でめくられようと確率には影響しないということなりそうです。
引用返信 編集キー/
■62687 / inTopicNo.7)  Re[4]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (54回)-(2011/10/27(Thu) 12:58:12)
> 最後がどう並んでいるかだけになるということになるのではないでしょうか。
shuさんありがとうございます。
感覚的に説得力の高い話で感覚的には納得です。
これを数式に翻訳するのは大変そうですができたらすごそうですね。
がんばって考えてみます。

k=mというのはkが一般の自然数というつもりでした。
m問題中で使ってますね。
引用返信 編集キー/
■62730 / inTopicNo.8)  Re[5]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (55回)-(2011/10/31(Mon) 08:33:11)
いろいろ数式変換を試したのですが難しいですね。
帰納法も駄目、nCrから導こうとしても式が複雑になって駄目。
ちょっと諦めようかと考えます。

あきらめるということで暫定的解決とします。
もし数式と考え方を提供してくださる方を期待してこのままにしておきます。
引用返信 編集キー/
■62731 / inTopicNo.9)  Re[6]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (56回)-(2011/10/31(Mon) 08:46:09)
新しい質問に移行します。
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0172
リンク先問題を解くコードで悩んでいます。
部屋の明りをのスイッチを入れたり消したりしながら部屋から部屋へ移動し、最後の部屋で全ての部屋の電気を消してかえれるか。
最短手順を求めよという問題です。


現在の解法は以下のとおりとなっています。
部屋から部屋へ移動する操作を深さ優先探索で求めます。
もし真っ暗な部屋に入ったら、過去の部屋にさかのぼってその部屋のスイッチを入れていたことにして先に進みます。

探索中は今まで入った部屋にあったスイッチをOffにできる部屋として全て覚えておき、その後スイッチの消せる部屋に入ったらその部屋のスイッチを消してはいけないので、ここまでの道の途中で消すことが出来ない部屋として保存しておきます。

このコードを5倍高速化出来たらたぶん合格できるのですが。
どなたかコードの改善点をご指摘いただけたらと思います。


一応今考えているのはルートの記憶を担当している変数
void rootSearch(rootMemo nowRoot
を
void rootSearch(rootMemo& nowRootにしてコピーのコストを低減する小手先の方法のみです。

#include<stdio.h>
#include<queue>
#include<string.h>
#include<vector>
#include<set>



struct switchMemo{
	//ある部屋で操作したスイッチの記録
	char onOf;
	int roomNo;
	bool operator<(const switchMemo& s)const{
		return roomNo<s.roomNo;
	}
};
struct rootMemo{
	int turn,lightState,nowRoomNo,lightOffMemo;//,roomsMemo
	//通ってきた部屋順と操作したスイッチを記録する
	std::vector<std::set<switchMemo> > switchsMemo;
	std::vector<int> root;
};

struct stateMemo{
	//同じ部屋で同じライトの状態でないかを記録し無限ループを防ぐ
	int nowRoomNo,lightState,lightOffMemo,turn;//roomsMemo;
	bool operator<(const stateMemo& sm)const{
		if(nowRoomNo!=sm.nowRoomNo)return nowRoomNo<sm.nowRoomNo;
		//if(roomsMemo!=sm.roomsMemo)return roomsMemo<sm.roomsMemo;
		if(lightOffMemo!=sm.lightOffMemo)return lightOffMemo<sm.lightOffMemo;
		return lightState<sm.lightState;
	}
	void set(rootMemo& nowRoot){
		nowRoomNo=nowRoot.nowRoomNo;
		lightState=nowRoot.lightState;
		//roomsMemo=nowRoot.roomsMemo;
		lightOffMemo=nowRoot.lightOffMemo;
		turn=nowRoot.turn; 
	}
};

//グローバル変数はここに集める
const int roomSize=16;
const char SwitchOn='n',SwitchOff='f',RoomMove='m';
std::set<switchMemo> dsets;
int n,m;
bool goalOK=false,allOfOK=false;
rootMemo ansRoot;
//ここまで


void print(rootMemo& ansRoot){
	//答えの出力
        std::set<switchMemo>::iterator it;
	printf("You can go home in %d steps.\n",ansRoot.turn);
	for(unsigned int i=0;i<ansRoot.root.size();i++){
		if(i!=0){
			printf("Move to room %d.\n",ansRoot.root[i]);
		}
		it=ansRoot.switchsMemo[i].begin();
		while(it!=ansRoot.switchsMemo[i].end()){
			if((*it).onOf==SwitchOn){
				printf("Switch on room %d.\n",(*it).roomNo);
			}else{
				printf("Switch off room %d.\n",(*it).roomNo);
			}
			it++;
		}
	}
}


bool backSwitchOn(rootMemo& rm,std::vector<int> roomSwitchs[roomSize]){
	unsigned int t1;
	int nowRoomNo=rm.nowRoomNo;
	//ある部屋に入った時その部屋が真っ暗なら過去にさかのぼってスイッチのあった部屋でスイッチを入れてたことにする。
	//スイッチが見つからず部屋に入ることが不可能ならfalseを返す
	switchMemo sm;
	sm.roomNo=nowRoomNo;
	sm.onOf=SwitchOn;
	for(int i=0;i<rm.root.size()-1;i++){
		t1=roomSwitchs[rm.root[i]].size();
		for(int j=0;j<t1;j++){
			if(nowRoomNo==roomSwitchs[rm.root[i]][j]){
				rm.lightState|=(1<<(nowRoomNo-1));
				rm.switchsMemo[i].insert(sm);
				rm.turn++;
				return true;
			}
		}
	}
	return false;
}
bool backSwitchAllOf(rootMemo& rm,std::vector<int> roomSwitchs[roomSize]){
	int lightState=rm.lightState,nowRoom,nowSwitchBit,nowSwitch;
	//最後の部屋に入った時過去にさかのぼり消しても問題のない部屋を全て消していたことにする。
	switchMemo sm;
	sm.onOf=SwitchOff;
	bool onOfOK[roomSize];
	memset(onOfOK,true,roomSize);
	std::set<switchMemo>::iterator it;
	for(int i=rm.root.size()-1;i>=0;i--){
		nowRoom=rm.root[i];
		onOfOK[nowRoom]=false;
		for(unsigned int j=0;j<roomSwitchs[nowRoom].size();j++){
			nowSwitch=roomSwitchs[nowRoom][j];
			nowSwitchBit=(1<<(nowSwitch-1));
			if((lightState&nowSwitchBit)!=0 && onOfOK[nowSwitch]==true){
				lightState=(lightState&(~nowSwitchBit));
				sm.roomNo=nowSwitch;
				rm.switchsMemo[i].insert(sm);
				rm.turn++;
			}
		}
	}
	if(lightState==(1<<(n-1)))return true;
	return false;
}


void rootSearch(rootMemo nowRoot,int nextRoom,
		bool roomConnect[roomSize][roomSize],std::vector<int> roomSwitchs[roomSize],std::set<stateMemo>& statesMemo){
		//深さ優先探索で部屋から部屋へ移動する

		//見つかった帰宅ルートより長くなったら別の探索へ
		if(ansRoot.turn<nowRoot.turn) return;
		//ゴール出来たら過去にさかのぼり電気を消せる部屋を全て消す
                if(nowRoot.nowRoomNo==n && backSwitchAllOf(nowRoot,roomSwitchs)==true){
			if(ansRoot.turn>nowRoot.turn){
				ansRoot=nowRoot;
				allOfOK=true;
				return;
			}
		}else if(nowRoot.nowRoomNo==n){
			goalOK=true;
		}
		
		stateMemo nowState;
		nowRoot.nowRoomNo=nextRoom;
		nowRoot.root.push_back(nowRoot.nowRoomNo);
		nowRoot.switchsMemo.push_back(dsets);
		nowRoot.turn++;

		if((nowRoot.lightState&(1<<(nowRoot.nowRoomNo-1)))!=0){
			//明るい部屋にそのまま移動
		}else if(backSwitchOn(nowRoot,roomSwitchs)){
			//もし部屋が暗かったら過去に辿った部屋でスイッチをつけていたことにして移動する
		}else{
			//過去にさかのぼってもスイッチを入れられなかったら
			return ;
		}
		

		for(unsigned int i=0;i<roomSwitchs[nowRoot.nowRoomNo].size();i++){
			nowRoot.lightOffMemo|=(1<<(roomSwitchs[nowRoot.nowRoomNo][i]-1));//消せるスイッチのリスト
		}
		nowRoot.lightOffMemo&=(~(1<<(nowRoot.nowRoomNo-1))); //今いる部屋は消せないので元に戻す
		nowState.set(nowRoot);
                //過去にさかのぼって消せる部屋のリストと今のライトのOnOff状態と今の部屋が同じになった状態がないかチェック
		if(statesMemo.find(nowState)==statesMemo.end()){
			statesMemo.insert(nowState);
		}else{
			int tTurn=(*statesMemo.find(nowState)).turn ;
			if(tTurn>nowState.turn){
				statesMemo.erase(nowState);
				statesMemo.insert(nowState); 
			}else{
				return;
			}
		}
		for(int i=1;i<=n;i++){
			if(roomConnect[nextRoom][i]){
				rootSearch(nowRoot,i,roomConnect,roomSwitchs,statesMemo);
			}
		}
}


void setData(){
	int p1,p2,roomLightState;//1bitずつ管理;
	bool roomConnect[roomSize][roomSize];
	
	std::vector<int> roomSwitchs[roomSize];
	
	
	
	memset(roomConnect,false,roomSize*roomSize);
	
	
	for(int i=0;i<m;i++){
		scanf("%d %d",&p1,&p2);
		roomConnect[p1][p2]=roomConnect[p2][p1]=true;
	}
	
	roomLightState=0;//ライトのオンオフ管理
	for(int i=1;i<=n;i++){
		scanf("%d",&p1);
		roomLightState|=p1==0?0:1<<(i-1);
	}
	int s,t;
	for(int i=1;i<=n;i++){
		scanf("%d",&s);
		for(int j=0;j<s;j++){
			scanf("%d",&t);
			if(i!=t){
				roomSwitchs[i].push_back(t);
			}
		}
	}
	//データの読み込み終わり
	//ここから深さ優先探索で部屋の移動をシミュレートするための初期データセット
	
	std::set<stateMemo> statesMemo;
	
	stateMemo nowState;
	rootMemo nowRoot,tempRoot;
	
	
	
	nowRoot.nowRoomNo=1;
	nowRoot.lightState=roomLightState;
	nowRoot.turn=-1;
	nowRoot.lightOffMemo=0;
	
	ansRoot.turn=1000000000;
	int nowRoomNo;
	
	
	bool moveOKs[roomSize];
	goalOK=allOfOK=false;
	
	memset(moveOKs,true,roomSize);
	//深さ優先探索
	rootSearch(nowRoot,1,roomConnect,roomSwitchs,statesMemo);

	if(allOfOK==true){
		print(ansRoot);
	}else if(goalOK==true){
		printf("You can not switch off all lights.\n");
	}else{
		printf("Help me!\n");
	}
}

int main(){
	while(1){
		scanf("%d %d",&n,&m);
		if(n==0 && m==0)break;
		setData();
	}
}

引用返信 編集キー/
■62732 / inTopicNo.10)  Re[7]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ shu (1060回)-(2011/10/31(Mon) 09:29:12)
No62731 (堀江伸一 さん) に返信

とき方ではありませんが、出口より遠い隣部屋のスイッチを各部屋につければ
要件を満たすには一番省エネだと思います。電気の入り切りを短時間に何回も行うと
かえって電気を消費してしまいます。

例でいけば2の部屋に1,3のスイッチ、4の部屋に2のスイッチ、4のスイッチは外
が一番よいと思います。
引用返信 編集キー/
■62733 / inTopicNo.11)  Re[8]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (58回)-(2011/10/31(Mon) 09:37:50)
そうですね。
それを言ったら最初の部屋と最後の部屋にだけスイッチをつければいいですね。
スイッチ最大数と部屋数と部屋のつながりが決まった時どの部屋からスタートしどの部屋がゴールでもスイッチのOnOff回数の総計が最小になるスイッチの配置は?

相当難しそうですが面白そうな問題になりますね。

でもこれは練習問題なので、題意に沿った返答を期待します。
練習問題としては少しコードサイズが大きく返答も大変だと思いますので気長にお待ちします。
引用返信 編集キー/
■62734 / inTopicNo.12)  Re[9]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (60回)-(2011/10/31(Mon) 10:53:03)
2011/10/31(Mon) 10:54:23 編集(投稿者)
やってみたら小手先のテクニックで解決できました。


解法
部屋数が少ないために一見計算量が小さく見えますが、部屋の明かりの状態が2^部屋数乗通りあるために見た眼よりも計算量が多い問題です。
意外と組み合わせ数が多くグラフに翻訳しても頂点数が「部屋の数*明りの状態^部屋数」ということになりグラフでも簡単には解けません。
また正解ルートを保持する必要があるためメモリ使用量制限32MB(個の問題は32Mb以上メモリを使ってはいけない)にも引っかかることになります。
このためグラフの手法も幅優先探索も使えません。

そこで手順数が最小になる手順になるルールに従ってのみ移動するという深さ優先探索の手法が有効になります。
解法は以下のとおりとなります。
部屋から部屋へ移動する操作を深さ優先探索で求めます。

移動中あった部屋のスイッチをON、Offできるスイッチとして覚えておきます。

探索中は今まで入った部屋にあったスイッチを全て覚えておきます。
これはスイッチをONOFFにできる部屋として記憶します。

移動中さかのぼってONOFF出来る部屋に入ったとします。
するとその部屋のスイッチは過去において消してはいけないので、消すことが出来ないスイッチなのでOnOff履歴からはずします。
もし真っ暗な部屋に入ったら、過去の部屋にさかのぼってその部屋のスイッチを入れていたことにして先に進みます。

後はこれを深さ優先探索として実装します。


//練習問題であるためいくつかの変数名を縮めています。


 #include<stdio.h>
 #include<queue>
 #include<string.h>
 #include<vector>
 #include<set>
 struct switchMemo{
	//ある部屋で操作したスウィッチの記録
	char onOf;
	int roomNo,turn;
	bool operator<(const switchMemo& s)const{
		return roomNo<s.roomNo;
	}
 };
 struct rootMemo{
	int turn,lightState,nowRoomNo,lightOffMemo;//
	//通ってきた部屋順と操作したスウィッチを記録する
	std::vector<std::set<switchMemo> > switchsMemo;
	std::vector<int> root;
 };
 struct stateMemo{
	//同じ部屋で同じライトの状態でないかを記録し無限ループを防ぐ
	int nowRoomNo,lightState,lightOffMemo,turn;//roomsMemo;
	bool operator<(const stateMemo& sm)const{
		if(nowRoomNo!=sm.nowRoomNo)return nowRoomNo<sm.nowRoomNo;
		//if(roomsMemo!=sm.roomsMemo)return roomsMemo<sm.roomsMemo;
		if(lightOffMemo!=sm.lightOffMemo)return lightOffMemo<sm.lightOffMemo;
		return lightState<sm.lightState;
	}
	void set(rootMemo& nowRoot){
		nowRoomNo=nowRoot.nowRoomNo;
		lightState=nowRoot.lightState;
		//roomsMemo=nowRoot.roomsMemo;
		lightOffMemo=nowRoot.lightOffMemo;
		turn=nowRoot.turn; 
	}
 };
 //グローバル変数はここに集める
 const int roomSize=16;
 const char SwitchOn='n',SwitchOff='f',RoomMove='m';
 std::set<switchMemo> dsets;
 int n,m;
 bool goalOK=false,allOfOK=false;
 rootMemo ansRoot;
 //ここまで
 void print(rootMemo& ansRoot){
	std::set<switchMemo>::iterator it;
	printf("You can go home in %d steps.\n",ansRoot.turn);
	for(unsigned int i=0;i<ansRoot.root.size();i++){
		if(i!=0){
			printf("Move to room %d.\n",ansRoot.root[i]);
		}
		it=ansRoot.switchsMemo[i].begin();
		while(it!=ansRoot.switchsMemo[i].end()){
			if((*it).onOf==SwitchOn){
				printf("Switch on room %d.\n",(*it).roomNo);
			}else{
				printf("Switch off room %d.\n",(*it).roomNo);
			}
			it++;
		}
	}
 }
 bool backSwitchOn(rootMemo& rm,std::vector<int> roomSwitchs[roomSize],switchMemo& bs){
	unsigned int t1;
	int nowRoomNo=rm.nowRoomNo;
	//ある部屋に入った時その部屋が真っ暗なら過去にさかのぼってスイッチのあった部屋でスイッチを入れてたことにする。
	//スイッチが見つからず部屋に入ることが不可能ならfalseを返す
	switchMemo sm;
	bs.roomNo=sm.roomNo=nowRoomNo;
	bs.onOf=sm.onOf=SwitchOn;
	for(int i=0;i<rm.root.size()-1;i++){
		t1=roomSwitchs[rm.root[i]].size();
		for(int j=0;j<t1;j++){
			if(nowRoomNo==roomSwitchs[rm.root[i]][j]){
				rm.lightState|=(1<<(nowRoomNo-1));
				rm.switchsMemo[i].insert(sm);
				rm.turn++;
				bs.turn=i;
				return true;
			}
		}
	}
	return false;
 }
 bool backSwitchAllOf(rootMemo rm,std::vector<int> roomSwitchs[roomSize]){
	int lightState=rm.lightState,nowRoom,nowSwitchBit,nowSwitch;
	//最後の部屋に入った時過去にさかのぼり消しても問題のない部屋を全て消していたことにする。
	switchMemo sm;
	sm.onOf=SwitchOff;
	bool onOfOK[roomSize];
	memset(onOfOK,true,roomSize);
	std::set<switchMemo>::iterator it;
	for(int i=rm.root.size()-1;i>=0;i--){
		nowRoom=rm.root[i];
		onOfOK[nowRoom]=false;
		for(unsigned int j=0;j<roomSwitchs[nowRoom].size();j++){
			nowSwitch=roomSwitchs[nowRoom][j];
			nowSwitchBit=(1<<(nowSwitch-1));
			if((lightState&nowSwitchBit)!=0 && onOfOK[nowSwitch]==true){
				lightState=(lightState&(~nowSwitchBit));
				sm.roomNo=nowSwitch;
				rm.switchsMemo[i].insert(sm);
				rm.turn++;
			}
		}
	}
	if(lightState==(1<<(n-1))){
		if(ansRoot.turn>rm.turn){
			ansRoot=rm;
			allOfOK=true;
		}
		return true;
	}
	return false;
 }
 void remove(rootMemo& nowRoot,switchMemo& backOnSwitch,stateMemo& backState){
	//この探索ルートは失敗なので元に戻す
	nowRoot.root.pop_back();
	nowRoot.nowRoomNo	=backState.nowRoomNo;
	nowRoot.lightState	=backState.lightState;
	nowRoot.lightOffMemo	=backState.lightOffMemo;
	nowRoot.turn		=backState.turn;	
	//スイッチを操作したが、過去と同じ状態になった
	if(backOnSwitch.turn!=-1){
		nowRoot.switchsMemo[backOnSwitch.turn].erase(backOnSwitch);
	}
 }
 void rootSearch(rootMemo& nowRoot,int nextRoom,
		bool roomConnect[roomSize][roomSize],std::vector<int> roomSwitchs[roomSize],std::set<stateMemo>& statesMemo);
		if(ansRoot.turn<=nowRoot.turn) return;
		if(nowRoot.nowRoomNo==n){
			int offs=nowRoot.lightOffMemo|(1<<(n-1));
			if((nowRoot.lightState&offs)==nowRoot.lightState && backSwitchAllOf(nowRoot,roomSwitchs)==true){
			}else{
				goalOK=true;
			}
		}
		stateMemo nowState,backState;
		switchMemo backSwitch;//もし駄目だった場合元に戻す
		backSwitch.turn=-1;
		backState.set(nowRoot);//失敗した場合元に戻すための記録
		nowRoot.nowRoomNo=nextRoom;
		nowRoot.root.push_back(nowRoot.nowRoomNo);
		while(nowRoot.root.size()>nowRoot.switchsMemo.size()){
			nowRoot.switchsMemo.push_back(dsets);
		}
		if((nowRoot.lightState&(1<<(nowRoot.nowRoomNo-1)))!=0){
			//明るい部屋にそのまま移動
		}else if(backSwitchOn(nowRoot,roomSwitchs,backSwitch)){
			//もし部屋が暗かったら過去に辿った部屋でスイッチをつけていたことにして移動する
		}else{
			remove(nowRoot,backSwitch,backState);
			return ;
		}
		nowRoot.turn++;//移動したのでターン数を増やす

		for(unsigned int i=0;i<roomSwitchs[nowRoot.nowRoomNo].size();i++){
			nowRoot.lightOffMemo|=(1<<(roomSwitchs[nowRoot.nowRoomNo][i]-1));//消せるスイッチのリスト
		}
		nowRoot.lightOffMemo&=(~(1<<(nowRoot.nowRoomNo-1))); //今いる部屋は消せないので元に戻す
		nowState.set(nowRoot);
		if(statesMemo.find(nowState)==statesMemo.end()){
			statesMemo.insert(nowState);
		}else{
			int tTurn=(*statesMemo.find(nowState)).turn ;
			if(tTurn>nowState.turn){
				statesMemo.erase(nowState);
				statesMemo.insert(nowState); 
			}else{
				remove(nowRoot,backSwitch,backState);
				return;
			}
		}
		for(int i=1;i<=n;i++){
			if(roomConnect[nextRoom][i]){
				rootSearch(nowRoot,i,roomConnect,roomSwitchs,statesMemo);
			}
		}
		remove(nowRoot,backSwitch,backState);
 }
 void setData(){
	int p1,p2,roomLightState;//1bitずつ管理;
	bool roomConnect[roomSize][roomSize];
	std::vector<int> roomSwitchs[roomSize];
	memset(roomConnect,false,roomSize*roomSize);
	for(int i=0;i<m;i++){
		scanf("%d %d",&p1,&p2);
		roomConnect[p1][p2]=roomConnect[p2][p1]=true;
	}
	roomLightState=0;//ライトのオンオフ管理
	for(int i=1;i<=n;i++){
		scanf("%d",&p1);
		roomLightState|=p1==0?0:1<<(i-1);
	}
	int s,t;
	for(int i=1;i<=n;i++){
		scanf("%d",&s);
		for(int j=0;j<s;j++){
			scanf("%d",&t);
			if(i!=t){
				roomSwitchs[i].push_back(t);
			}
		}
	}
	//データの読み込み終わり
	//ここから深さ優先探索で部屋の移動をシミュレートするための初期データセット
	std::set<stateMemo> statesMemo;	
	stateMemo nowState;
	rootMemo nowRoot,tempRoot;
	nowRoot.nowRoomNo=1;
	nowRoot.lightState=roomLightState;
	nowRoot.turn=-1;
	nowRoot.lightOffMemo=0;
	ansRoot.turn=1000000000;
	int nowRoomNo;
	goalOK=allOfOK=false;
	//深さ優先探索
	rootSearch(nowRoot,1,roomConnect,roomSwitchs,statesMemo);
	if(allOfOK==true){
		print(ansRoot);
	}else if(goalOK==true){
		printf("You can not switch off all lights.\n");
	}else{
		printf("Help me!\n");
	}
 }
 int main(){
	while(1){
		scanf("%d %d",&n,&m);
		if(n==0 && m==0)break;
		setData();
	}
 }

引用返信 編集キー/
■62735 / inTopicNo.13)  Re[9]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (61回)-(2011/10/31(Mon) 10:54:33)
2011/10/31(Mon) 10:57:16 編集(投稿者)
2011/10/31(Mon) 10:57:14 編集(投稿者)

上記コードはもっと高速化できるようです。
もっと賢い考え方があればご指摘お願いします。
引用返信 編集キー/
■62736 / inTopicNo.14)  Re[10]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (62回)-(2011/10/31(Mon) 11:22:51)
2011/10/31(Mon) 12:26:48 編集(投稿者)
2011/10/31(Mon) 12:22:36 編集(投稿者)

もしかしたらグラフの問題として解けるのかもしれません。
他の正答者のコードを読む限り半分〜1/3のコード量でこの問題を解けるようです。

この問題幅優先探索で解いてるコードがありましたがコードが難しそうでちょっと読み解けません。
リンク先で簡単な解説を頂けたらと思います。
http://d.hatena.ne.jp/k_operafan/20110201/1296523264#c1320028130


募集ということで次の問題の質問です。

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0531
この問題は前回私がこの掲示板で質問したシートの問題に似ています。
しかしシートのときと違い縦横サイズが半端ありません。

シートの時のように1列ごとに探索切りにしても横幅が100万と計算量が爆発します。
100万*1000=10億回と計算量的に間に合わないことになります。

グラフに変換として解こうとしても4枚のテープが四角形を内部に作る場合と作らない場合のように重なりをどう判断するかという問題がでます。
四角形を作っても別のマスキングテープがその四角形を覆ったりする可能性もあります。

マスキングテープの枚数が少ないことを利用して解くのだと思いますが、テープの重なりをどう考えればよいのか思いつかない状態です。

よい考え方などないものでしょうか?
引用返信 編集キー/
■62737 / inTopicNo.15)  Re[11]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (63回)-(2011/10/31(Mon) 12:51:29)
少し考えてみました。
マスキングテープを点、外周を点としテープとテープか外周が重なりか接すれば点をつなげます。
グラフの定理
頂点数+面の数-辺の数=2より
面の数=2+辺の数-頂点数
が成り立ちます。

点が三角形をなす場合その三角形で囲まれた面はテープに覆われて存在しないので面の数を減らします。

グラフの中にある三角形の個数をtとして
必要な色の数=面の数なので

面の数=2+辺の数-頂点数-グラフの中にある三角形の数
と考えました。

三角形かどうかは任意の三点を選び、3点から出る辺が互いに他の2点を繋げているなら三角形であると判別する。

よって計算量は頂点数^3*3点に入る辺の数
どなたか検証していただけないでしょうか?
引用返信 編集キー/
■62744 / inTopicNo.16)  Re[12]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (65回)-(2011/10/31(Mon) 15:53:59)
うーん難しいのです。
出来るグラフの三角形が重複しないときはいいのだけど何枚も重なって三角形が重複した場合の計算が難しい。
考え方が間違ってる?


#include<stdio.h>
#include<set>
#include<string.h>

const int mymax=1006;
bool moveOKs[mymax];

bool connectOK(int xs[2],int ys[2],int xs2[2],int ys2[2]){
return (xs[0]<=xs2[1] && ys[0]<=ys2[1] && xs2[0]<=xs[1] && ys2[0]<=ys[1]);
}

void connectSearch(std::set<int> con[mymax],int now){
std::set<int>::iterator it=con[now].begin();
moveOKs[now]=false;
while(it!=con[now].end()){
if(moveOKs[(*it)]==true)connectSearch(con,(*it));
it++;
}
}


void setData(int w,int h){

int n,tapeXs[mymax][2],tapeYs[mymax][2];
memset(moveOKs,true,mymax);

std::set<int> connect[mymax];
;

int edgeCount=0;

scanf("%d",&n);
n+=4;//外周を4枚のマスキングテープとして考える
//左端
tapeXs[0][0]=tapeYs[0][0]=0;
tapeXs[0][1]=0,tapeYs[0][1]=h;
//下
tapeXs[1][0]=tapeYs[1][0]=0;
tapeXs[1][1]=w,tapeYs[1][1]=0;
//上
tapeXs[2][0]=0,tapeYs[2][0]=h;
tapeXs[2][1]=w,tapeYs[2][1]=h;
//右
tapeXs[3][0]=w,tapeYs[3][0]=0;
tapeXs[3][1]=w,tapeYs[3][1]=h;

for(int i=0;i<n;i++){
if(i>3)scanf("%d %d %d %d",&tapeXs[i][0],&tapeYs[i][0],&tapeXs[i][1],&tapeYs[i][1]);
for(int j=0;j<i;j++){
if(connectOK(tapeXs[i],tapeYs[i],tapeXs[j],tapeYs[j])){
connect[i].insert(j);
connect[j].insert(i);
edgeCount++;
}
}
}

int triangleCount=0;
int spentPoint[mymax];
memset(spentPoint,0,mymax);

for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
for(int k=j+1;k<n;k++){
if( connect[i].find(j)!=connect[i].end()
&& connect[i].find(k)!=connect[i].end()
&& connect[j].find(i)!=connect[j].end()
&& connect[j].find(k)!=connect[j].end()
&& connect[k].find(i)!=connect[k].end()
&& connect[k].find(j)!=connect[k].end())
{
triangleCount++;
}
}
}
}
int sum=0;
for(int i=0;i<n;i++){
if(moveOKs[i]){
sum++;
connectSearch(connect,i);
}
}
printf("%d\n",sum+edgeCount-n-triangleCount);
}




int main(){
int w,h;
while(1){
scanf("%d %d",&w,&h);
if(w==0 && h==0) break;
setData(w,h);
}
}
引用返信 編集キー/
■62754 / inTopicNo.17)  Re[13]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (67回)-(2011/10/31(Mon) 21:47:53)
2011/11/02(Wed) 14:40:58 編集(投稿者)

グラフの中にある面は各森で

一つの森で必要な色の数=1+辺の数-頂点数-グラフ中の三角形の数
この計算式が全ての森で成り立つので

必要な色の数=森の数+全ての辺の数-全頂点数-全三角形の数

となると考えたのですが。
うまくいきません。

本当に必要な式は

必要な色の数=森の数+全ての辺の数-全頂点数-三角形の数+三角形が重複する部分

なのですが三角形の重複部分を求めるのはかなり難しい問題です。
ノートの上にいくつもグラフを書いたり辺を足したり引いたりしたのですが、グラフ理論など習ったこともない身としては法則性が見えません。

この質問は数学といっても変わりない問題になったのでリンク先でも質問することにしました。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1174610546
引用返信 編集キー/
■62819 / inTopicNo.18)  Re[14]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (68回)-(2011/11/04(Fri) 09:08:07)
2011/11/04(Fri) 09:12:08 編集(投稿者)
結局グラフで解く方法を思いつかず前回この板で質問したシートの周計を求める問題を応用して解きました。

列を比べた時同じ状態の列を無視し隣の列とエリアを結合して数え上げる方法をとりました。

コードが膨らむのは思考力やプログラマとしての実力が低いからです。
コード実行速度40位中3位ですが、他の方のコードサイズを見る限り私のコードは無意味に膨らんでる感じもします。
もっと簡単な考え方で解けると思うのでアドバイスお願いします。





#include<stdio.h>
#include<vector>
#include<set>
#include <algorithm>

struct point{
	int x1,x2,y,inOut;
	bool operator<(const point p)const{
		if(y!=p.y)return y<p.y;
		return inOut>p.inOut;
	}
	void set(int xi,int xi2,int yi,int inOuti){
		x1=xi;
		x2=xi2;
		y=yi;
		inOut=inOuti;
	}
};

std::vector<bool> moveOKs;

void connectSearch(std::vector<std::set<int> >& con,int now){
	std::set<int>::iterator it=con[now].begin();
	moveOKs[now]=false;
	while(it!=con[now].end()){
		if(moveOKs[(*it)]==true)connectSearch(con,(*it));
		it++;
	}
}

void setData(int w,int h){
	int n;
	point p;
	scanf("%d",&n);
	int x1,y1,x2,y2;
	std::vector<point> points;
	std::set<int> xs;
	moveOKs.clear();
	
	for(int i=0;i<n;i++){
		scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
		p.set(x1,x2,y1,1);
		points.push_back(p);
		p.set(x1,x2,y2,-1);
		points.push_back(p);
		xs.insert(x1);
		xs.insert(x2);
	}
	xs.insert(0);
	p.set(0,w,0,1);
	points.push_back(p);
	p.set(0,w,0,-1);
	points.push_back(p);
	p.set(0,w,h,1);
	points.push_back(p);
	
	std::sort(points.begin(),points.end());
	std::set<int>::iterator it=xs.begin();
	int nowX,colorCount=0,size;
	int inOutCount,now,old,turn=0;
	std::vector<int> yMemo[2];
	std::vector<int> yNoMemo[2];
	
	std::vector<std::set<int> > connect;
	std::set<int> dammySet;
	
	while(it!=xs.end()){
		nowX=(*it);
		inOutCount=0;
		now=turn%2;
		old=(turn+1)%2;
		turn++;

		yMemo[now].clear();
		yNoMemo[now].clear();
		size=points.size()-1;
		
		
		for(int i=0;i<size;i++){
			if(nowX<points[i].x1 || points[i].x2<=nowX)continue;
			//printf("(%d %d %d %d %d) \n",i,points[i].x1,points[i].x2,points[i].y,points[i].inOut);
			inOutCount+=points[i].inOut;
			if(inOutCount==0){
				yMemo[now].push_back(points[i  ].y);
				int tempy=points[i].y;
				while(tempy==points[i].y || nowX<points[i].x1 || points[i].x2<=nowX){
					i++;
					if(points[i].x1<=nowX && nowX<points[i].x2){
							inOutCount+=points[i].inOut; 
							//printf("(%d %d %d %d %d) \n",i,points[i].x1,points[i].x2,points[i].y,points[i].inOut);
					}
				}
				yMemo[now].push_back(points[i].y);
				yNoMemo[now].push_back(-1);
			}
		}
		//printf("\n%d\n",yMemo[now].size());
		if(yMemo[now].empty()){
			it++;
			continue;
		}
		/*
		for(int i=0;i<yMemo[now].size();i+=2){
			printf("(%d %d)",yMemo[now][i],yMemo[now][i+1]);
		}
		printf("\n");
		*/
		size=yMemo[now].size();
		unsigned int oldP=0,nowP=0;
		int y1,y2,y3,y4;
		while(oldP<yMemo[old].size() && nowP<yMemo[now].size()){
			y1=yMemo[old][oldP  ];
			y2=yMemo[old][oldP+1];
			y3=yMemo[now][nowP  ];
			y4=yMemo[now][nowP+1];
			if(y2<=y3){
				oldP+=2;
			}else if(y1>=y4){
				if(yNoMemo[now][nowP/2]==-1){
					yNoMemo[now][nowP/2]=colorCount;
					connect.push_back(dammySet);
					colorCount++;
					moveOKs.push_back(true);
				}
				nowP+=2;
			}else{
				if(yNoMemo[now][nowP/2]==-1){
					yNoMemo[now][nowP/2]=yNoMemo[old][oldP/2];
				}else{
					connect[yNoMemo[now][nowP/2]].insert(yNoMemo[old][oldP/2]);
					connect[yNoMemo[old][oldP/2]].insert(yNoMemo[now][nowP/2]);
				}
				if(y2>y4){
					nowP+=2;
				}else if(y2<y4){
					oldP+=2;
				}else{
					nowP+=2;
					oldP+=2;
				}
			}
		}
		for(unsigned int i=nowP;i<yMemo[now].size();i+=2){
			if(yNoMemo[now][i/2]==-1){
				yNoMemo[now][i/2]=colorCount;
				connect.push_back(dammySet);
				colorCount++;
				moveOKs.push_back(true);
			}
		}
		it++;
	}
	int sum=0;
	for(unsigned int i=0;i<moveOKs.size();i++){
		if(moveOKs[i]==true){
			sum++;
			connectSearch(connect,i);
		}
	}
	printf("%d\n",sum);
}




int main(){
	int w,h;
	while(1){
		scanf("%d %d",&w,&h);
		if(w==0 && h==0) break;
		setData(w,h);
	}
}

引用返信 編集キー/
■62826 / inTopicNo.19)  Re[15]: プログラムの練習問題をゆっくりと考えていきます
□投稿者/ 堀江伸一 (69回)-(2011/11/04(Fri) 19:16:03)
2011/11/04(Fri) 19:17:57 編集(投稿者)

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0537
この問題に挑戦してみました。

解説はあるのですが何やら難しげ。
http://www.ioi-jp.org/joi/2008/2009-yo-prob_and_sol/2009-yo-t6/review/2009-yo-t6-review.html
ちょっと理解できてない状態です。

アドバイス待ってます。
初めは解法1の方法で挑戦して玉砕しました。
解法2を理解したいところです。

ただ計算するだけなら可能ですが模範解答のように計算量を抑えるとなると難しい感じです。
引用返信 編集キー/
■62841 / inTopicNo.20)  Re[16]: プログラムの練習問題をゆっくりと考えていきます
 
□投稿者/ 堀江伸一 (70回)-(2011/11/05(Sat) 13:43:18)
2011/11/05(Sat) 18:31:42 編集(投稿者)



fi(x, y)=fi(x-1, y-(n×n-i+1))+fi-1(x-1, y-(n×n-i+1))
は足し算である以上集合を分割していると考えればよい様な気がします。

@ fi(x-1, y-(n×n-i+1))の
x-1でbの合計が一つ減り、y-(n*n-i+1)でaの合計がn*n-i+1だけへります。
するとbi番目の数字だけが1減ります。

これで@で可能な組み合わせ数はbiの最大値の要素だけが一つだけ減った状態での組み合わせになります。

この減った組み合わせを補てんするために
fi-1(x-1, y-(n×n-i+1))が存在する。

そう理解しました。

模範解答で問題になるのは関数fiのi,x,yの組み合わせがどうなった時に再起をやめて組み合わせ数を計算して返すかという点になります。
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -