tozangezan's diary

勝手にソースコードをコピペして利用しないでください。

トロントに行きたいけどGCJは無理そうだからDCJの対策をする狼。三日目。

median

これも相当厄介(めんどくささが)。配列がランダムなことからハッシュが有効だというのがわかる。さらに、この問題ではかつてのプラクティスにあったshhhで使ったテクがかなり有効利用できる。
しかしいかんせん実装量が多くて複雑でテストが微妙なので、例によって通るかどうかヒヤヒヤな問題...。なんか一箇所変えたらACになったが。変えた場所がどういう意味を持ってるのかはわからん。
変なマジックナンバーを持ち出したいときはちゃんと素数をもってきましょう。

long long D=1000000000000000LL;
pair<wolf,long long> ev[1100];
int ps[1100];
wolf pk[1100];
int rn[1100000];
int vsz[1100];
int F=33000;
int cnt[40000];
int main(){
	int T=NumberOfNodes();
	int I=MyNodeId();
	if(I==T-1){
		for(int i=0;i<1000000;i++){
			rn[i]=GetData((long long)(xrand()%1000000000000000LL));
		}
		std::sort(rn,rn+1000000);
		int Cnt=0;
		for(int i=0;i<1000000;i++){
			if(i&&rn[i]==rn[i-1])Cnt++;
			else Cnt=1;
			if(Cnt>600000){
				printf("%d\n",rn[i]);
				for(int i=0;i<T-1;i++){
					PutInt(i,0);
					Send(i);
				}
				return 0;
			}
		}
		for(int i=0;i<T-1;i++){
			PutInt(i,1);
			Send(i);
		}
		int NK=1000;
		for(int i=0;i<NK;i++){
			long long st=i*1000037;
			wolf key=0;
			wolf ks=1;
			wolf mul=1145141919;
			for(int j=0;j<100;j++){
				key*=mul;
				ks*=mul;
				key+=GetData(D+st+j);
			}
			ev[i]=make_pair(key,st);
		}
		std::sort(ev,ev+NK);
		int sz=0;
		for(int i=0;i<NK;i++){
			if(i==0||ev[i].first!=ev[i-1].first){
				pk[sz]=ev[i].first;
				ps[sz]=ev[i].second;
				sz++;
			}
		}
		for(int i=0;i<T-1;i++){
			PutInt(i,sz);
			for(int j=0;j<sz;j++){
				PutLL(i,pk[j]);
				PutLL(i,ps[j]);
			}
			Send(i);
		}
		long long tot=0;
		for(int i=0;i<T-1;i++){
			Receive(i);
			for(int j=0;j<33000;j++){
				int tmp=GetInt(i);
				cnt[j]+=tmp;
				tot+=tmp;
			}
		}
		long long now=0;
		int pind=0;
		int qind=0;
		for(int i=0;i<33000;i++){
			now+=cnt[i];
			if(now*2>tot){
				pind=i;
				now-=cnt[i];
				break;
			}
		}
		for(int i=0;i<T-1;i++){
			PutInt(i,pind);
			Send(i);
		}
		//tot=0;
		for(int i=0;i<33000;i++)cnt[i]=0;
		for(int i=0;i<T-1;i++){
			Receive(i);
			for(int j=0;j<33000;j++){
				int tmp=GetInt(i);
				cnt[j]+=tmp;
				//tot+=tmp;
			}
		}
		//now=0;
		for(int i=0;i<33000;i++){
			now+=cnt[i];
			if(now*2>tot){
				qind=i;break;
			}
		}
		printf("%d\n",pind*33000+qind);
	}else{
		Receive(T-1);
		int ch=GetInt(T-1);
		if(ch==0)return 0;
		Receive(T-1);
		int sz=GetInt(T-1);
		for(int i=0;i<sz;i++){
			pk[i]=GetLL(T-1);
			ps[i]=GetLL(T-1);
		}
		wolf key=0;
		wolf ks=1;
		wolf mul=1145141919;
		for(int i=0;i<100;i++){
			key*=mul;
			ks*=mul;
			key+=GetData(D+i);
		}
		long long ind=0;
		long long os=0;
		while(1){
			int at=lower_bound(pk,pk+sz,key)-pk;
			if(pk[at]==key){
	//			printf("%d %lld\n",ps[at],ind);
				os=ps[at]-ind;
				break;
			}
			key*=mul;
			key+=GetData(D+ind+100);
			key-=ks*GetData(D+ind);
			ind++;
		}
	//	printf("%lld\n",(os+11)%11);
		int L=sz*I/(T-1);
		int R=sz*(I+1)/(T-1);
		for(int i=L;i<R;i++){
			key=0;
			ks=1;
			mul=1145141919;

			for(int j=0;j<100;j++){
				key*=mul;
				ks*=mul;
				key+=GetData(D+ps[i]-os+j);
			}
			while(1){
				key*=mul;
				key+=GetData(D+ps[i]-os+vsz[i]+100);
				int tmp=GetData(D+ps[i]-os+vsz[i]);
				cnt[tmp/F]++;
				key-=ks*tmp;
				vsz[i]++;
				if(binary_search(pk,pk+sz,key)){
					break;
				}
			}
		}
		for(int i=0;i<33000;i++){
			PutInt(T-1,cnt[i]);
		}
		Send(T-1);
		for(int i=0;i<33000;i++)cnt[i]=0;
		Receive(T-1);
		int tar=GetInt(T-1);
		for(int i=L;i<R;i++){
			for(int j=0;j<vsz[i];j++){
				int tmp=GetData(D+ps[i]-os+j);
		//		printf("%d %lld\n",i,((ps[i]+j)%17+17)%17);
				if(tmp/F==tar)cnt[tmp%F]++;
			}
		}
		for(int i=0;i<33000;i++){
			PutInt(T-1,cnt[i]);
		}
		Send(T-1);
	}
}

トロントに行きたいけどGCJは無理そうだからDCJの対策をする狼。二日目。

baby_blocks

ヤバすぎるTime Limit設定に全アフリカが泣いた。(実は3000万回クエリ読んでも1.8秒しかかからないという罠)
偏りすぎるのがやばいけどどうすんの、ってT個のブロックに等分した後左右から別のブロックに移動するタイミング約2N個をイベントソートして先頭N個だけ使えばいいんですね。
全く難しいアイデアではないし、異様にきつい制限時間だけが厄介。本番でこれ落とした人可哀想www何この11位の人画面の隅に変なキャラクター出しながらコーディングしてるんだけどうけるwwwww

メモ: 司令塔作るときは0番ノードよりT-1番ノードにした方がやりやすい。

#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<deque>
#include<stack>
#include<string>
#include<string.h>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#include<stdlib.h>
#include<cassert>
#include<time.h>
#include<bitset>
#include<message.h>
#include"baby_blocks.h"
using namespace std;
const long long mod=998244353;
const long long inf=mod*mod;
const long long d2=(mod+1)/2;
const long double EPS=1e-10;
const long double PI=acos(-1.0);
int ABS(int a){return max(a,-a);}
long long ABS(long long a){return max(a,-a);}
long double ABS(long double a){return max(a,-a);}

/*
USER'S GUIDE

Num of data / node: 1000
Amount of data / node: 8MB
Send, Receive / 5 ~ 10ms?
Linear Message passing: slow (500ms through every nodes)

HOW TO USE TOO COMPLICATED AND TOO DIFFICULT LIBRARIES
python dcj/dcj.py test --source QUESTIONNAME.cpp --nodes NUMBER_OF_NODES

FUNCTION LIBRARIES :
int NumberOfNodes();
int MyNodeId();
void PutChar(int target, char value);
void PutInt(int target, int value);
void PutLL(int target, long long value);
void Send(int target); : call this after using Put***
int Receive(int source); call this before using Get*** (return value: number of sended values)
char GetChar(int source);
int GetInt(int source);
long long GetLL(int source);
*/
long long b_sum[110];
long long a_sum[110];
long long l_sum[110];
long long r_sum[110];
int val[10100000];
pair<long long,int> ev[210];
int main(){
	int T=NumberOfNodes();
	int I=MyNodeId();
	int N=GetNumberOfBlocks();
	int bk=T;
	if(N<T){
		T=N+1;
		if(I>=T)return 0;
	}
	
	if(I==T-1){
		for(int i=0;i<T-1;i++){
			Receive(i);
			b_sum[i]=GetLL(i);
		}
		for(int i=0;i<T-1;i++){
			l_sum[i]=r_sum[i]=b_sum[i];
		}
		for(int i=0;i<T-1;i++){
			l_sum[i+1]+=l_sum[i];
		}
		for(int i=T-2;i>=0;i--){
			r_sum[i]+=r_sum[i+1];
		}
		for(int i=0;i<T-1;i++){
			ev[i*2]=(make_pair(l_sum[i],0));
			ev[i*2+1]=(make_pair(r_sum[i],1));
		}
		std::sort(ev,ev+T*2-2);
		long long Loff=0;
		long long Roff=0;
		int l_bl=0;
		int r_bl=T-2;
	//	for(int i=0;i<T;i++){
	//		printf("%lld %d\n",ev[i].first,ev[i].second);fflush(stdout);
	//	}
		for(int i=0;i<T-1;i++){

			PutLL(i,Loff);
			PutLL(i,Roff);
			PutInt(i,l_bl);
			PutInt(i,r_bl);
			Send(i);

			if(ev[i].second==0){
				Loff=ev[i].first;
				l_bl++;
			}else{
				Roff=ev[i].first;
				r_bl--;
			}
		}
		int ret=0;
		for(int i=0;i<T-1;i++){
			Receive(i);
			ret+=GetInt(i);
		}
		printf("%d\n",ret-1);
	}else{
		long long L=(long long)N*I/(T-1);
		long long R=(long long)N*(I+1)/(T-1);
		for(int i=L;i<R;i++){
			int q=GetBlockWeight(i);
			b_sum[I]+=q;
		}
		PutLL(T-1,b_sum[I]);
		Send(T-1);
		Receive(T-1);
		long long Loff=GetLL(T-1);
		long long Roff=GetLL(T-1);
		int l_bl=GetInt(T-1);
		int r_bl=GetInt(T-1);
		long long at_L=(long long)N*l_bl/(T-1);
		long long at_R=(long long)N*(r_bl+1)/(T-1)-1;
		long long n_L=(long long)N*(l_bl+1)/(T-1);
		long long n_R=(long long)N*(r_bl)/(T-1)-1;
		int ret=0;
		while(1){
			if(at_L==n_L||at_R==n_R||at_L>at_R+1)break;
		//	printf("%lld %lld %lld %lld\n",at_L,at_R,n_L,n_R);
			if(Loff==Roff){
				ret++;
				Loff+=GetBlockWeight(at_L);
				at_L++;
				Roff+=GetBlockWeight(at_R);
				at_R--;
			}else if(Loff<Roff){
				Loff+=GetBlockWeight(at_L);
				at_L++;
			}else{
				Roff+=GetBlockWeight(at_R);
				at_R--;
			}
		}
		PutInt(T-1,ret);
		Send(T-1);
	}

}

トロントに行きたいけどGCJは無理そうだからDCJの対策をする狼。一日目。

broken_memory

二分探索で変なところは探せる。3個以上のノードのデータをまとめれば、全ての答えがわかる。
hashingが難しい。2個の間違ったデータを含むときと1個も含まないときでハッシュ値がちゃんと異なるように設定しよう。

#include<stdio.h>
#include<algorithm>
#include<message.h>
#include<set>
#include"broken_memory.h"
using namespace std;

/*
USER'S GUIDE

Num of data / node: 1000
Amount of data / node: 8MB
Send, Receive / 5 ~ 10ms?
Linear Message passing: slow (500ms through every nodes)

HOW TO USE TOO COMPLICATED AND TOO DIFFICULT LIBRARIES
python dcj/dcj.py test --source QUESTIONNAME.cpp --nodes NUMBER_OF_NODES

FUNCTION LIBRARIES :
int NumberOfNodes();
int MyNodeId();
void PutChar(int target, char value);
void PutInt(int target, int value);
void PutLL(int target, long long value);
void Send(int target); : call this after using Put***
int Receive(int source); call this before using Get*** (return value: number of sended values)
char GetChar(int source);
int GetInt(int source);
long long GetLL(int source);
*/
long long in[10100000];
long long xs[10100000];
int fn[1100][2];
int pp[1100];
int ans[1100];
int main(){
	int T=NumberOfNodes();
	int I=MyNodeId();
	int N=GetLength();
	for(int i=0;i<N;i++){
		in[i]=GetValue(i);
		in[i]*=(i*i*9+i*13+14);
		in[i]+=(in[i]<<11)+(in[i]<<22)+(in[i]>>15);
		xs[i+1]=xs[i]^in[i];
	}
	if(I%5==0){
		for(int ii=1;ii<5;ii++){
			int i=I+ii;
			int left=-1;
			int right=N;
			long long tmp=0;
			while(left+1<right){
				int M=(left+right)/2;
				PutInt(i,M);
				Send(i);
				Receive(i);
				long long ff=GetLL(i);
				if(ff!=xs[M+1]){
					right=M;
					tmp=ff;
				}else{
					left=M;
				}
			}
			fn[i][0]=right;
			left=right;
			right=N;
			while(left+1<right){
				int M=(left+right)/2;
				PutInt(i,M);
				Send(i);
				Receive(i);
				long long ff=GetLL(i)^tmp;
				if(ff!=(xs[M+1]^xs[fn[i][0]+1])){
					right=M;
				}else{
					left=M;
				}
			}
			fn[i][1]=right;
			PutInt(i,1145141919);
			Send(i);
		}

		int sz=0;
		for(int ii=1;ii<5;ii++){
			int i=I+ii;
			for(int j=0;j<2;j++){
				pp[sz++]=fn[i][j];
			}
		}
		std::sort(pp,pp+sz);
		for(int i=1;i<sz;i++){
			if(pp[i]==pp[i-1]){
				ans[I]=pp[i];break;
			}
		}
		for(int ii=1;ii<5;ii++){
			int i=I+ii;
			for(int j=0;j<2;j++){
				if(fn[i][j]!=ans[I]){
					ans[i]=fn[i][j];
				}
			}
		}
		if(I==0){
			for(int i=5;i<T;i+=5){
				Receive(i);
				for(int j=0;j<5;j++){
					ans[i+j]=GetInt(i);
				}
			}
			for(int i=0;i<T;i++){
				if(i)printf(" ");
				printf("%d",ans[i]);
			}
			printf("\n");
		}else{
			for(int i=0;i<5;i++){
				PutInt(0,ans[I+i]);
			}
			Send(0);
		}

	}else{
		while(1){
			Receive(I-I%5);
			int at=GetInt(I-I%5);
			if(at==1145141919){
				break;
			}else{
				PutLL(I-I%5,xs[at+1]);
				Send(I-I%5);
			}
		}
	}
}

AGC022Cを解くのが流行っているので流行に乗った

こんなことに時間を使ってないで研究しろ

問題
C - Remainder Game

ソースコード
Submission #2409669 - AtCoder Grand Contest 022


メモ:

15:50 それでは、問題やります
15:52 問題を読んで入力しました(IMEが壊れた...)
15:53 とりあえず各操作は最大1回、greedyに最大のkを決めて好き放題判定していくだけ, nはnかn/2以下にできる
15:54 IMEが壊れてやりにくすぎる、再起動します
15:56 なんかおかしくて再起動ができない
15:58 もどってきました
16:01 最短路のor取るだけな気がしてきた

7 5
3 1

16:04 greedyの2択がめんどいんだよな
16:06 全部管理すれば良い、解けた
16:13 サンプルが通らんよ
16:15 なんでWAなんだよ 2ケースだけだから変なものあるんかな
16:18 どうせ-1のケースでしょ→やっぱりな

典型要素:

  • 2^nで1回ずつ使うやつはgreedy
  • 両側貼り合わせ
  • パソコン操作

両側貼り合わせって要は「スタートからここまで行く方法に関する情報」「ここからゴールまで行く方法に関する情報」をなんかして持っておいて途中の判定とかに使うやつなんですけども、これはAGCでばかり見ます。

圧倒的に遅いんですけども、どこが遅いのかよくわからん(最初のパソコン壊れによる消耗はかなりあるけどもそれでも遅い)。やっぱりAGCはダメですね。

2018年の目標

"一年は目標に始まり反省に終わる。"
  ーー Tozan Southerpacks

書きなぐりと言われても否定できませんが、今年の目標も書いていきましょう。全然意識してないから達成できないんだという話はしてはいけません。

競技プログラミング

枠の数が正常のオンサイトに出る

4年連続でいけたら一流の競技プログラマーでしょ。

なんか大会で優勝する

国内オンサイトかな? ひそかに狙っていきたいと思いますが、ちゃんと個数は確保されるんですかね?

天才以外お断り問題たちに光明を見つける

なんかAGCの問題への見方に変化があると嬉しいですね。

お勉強

研究

1本は雑誌に投稿したいので頑張ります。2本目が収拾つくとなお良いですね。
将来的にどの分野に進みたいかをちゃんと考えられるようにしておこう。

英語

去年はVocabularyの一年でした。当然speakingが今至急なのでspeakingを鍛える。ひとまず海外に行っても研究生活とか困らずできるレベルになるのが目標です(がこんなのどうやって評価すればいいんだ)。
あとwriting、時間との相談要素もありますが、AtCoderの問題文英訳でも関わったりとかもしたい。

他の言語

ロシア語を中級レベル目指そう。なんかあんまり国内で人気のない言語をまともなレベルまでわかるようになろう。

市町村系

また変な場所を目指して旅に出る

ここで言わなくてもやります。

🐺

🐺

いやこれ書く必要ないわ

2017年の反省

"一年は、目標に始まり、反省に終わる。"
  ーー Tozan Southerpacks

冗談はさておき、半ば無茶振りのような今年の目標を一つ一つ振り返っていきましょう。

競技プログラミング

Div1 Hard のAC数を450以上にする

2013年以降の CF の Div1 コンテストは全部 Virtual Participation する

OpenCup にちゃんと出る

ちゃんと出たかな? きわどいところだけど、自分では頑張ったつもりなのでぎりぎり合格点。
最近の目標はjapan02に勝つことですが、自分のできることが限られすぎていて勝敗への影響があんまり大きくないです。安定性への影響はあるかも。

枠の数が正常のオンサイトに出る

DCJ笑 マジ何なんだあれ

自分でも良問と思える問題を3問以上作る

作問は向いてないのがわかったのでもう作問を目標にするのはやめます。

TC Highest 2800以上、CF Highest 2800以上、AtCoder Highest 3200以上

笑 というか笑ってばかりいないでTCとかCFをやれというはなし 絶対真面目にやればこの二つはいけたでしょ

学業

研究して院試して卒業する

多分ヨユー 卒業する前に一本投稿できるとなお良いけど、間に合うか微妙

非ネイティブとしては申し分ない程度の英単語力と、まあ全般的になんとかやっていける程度の英語力をつける

英単語力はもういらんわ 文語的な単語はネイティブと勝負できるね
結局何年経とうとspeaking力だけはどうにかなる下限で選択肢が減りまくりなのでこれだけは何とかしないといけないんだけど何ともできないから助けて
海外のPhDも学校選べば英語力の観点ではspeaking以外は無問題だしspeakingだけ何とかなれば*1もう問題ないでしょ 研究頑張ろう

その他

Youtube 動画...?

時間帯効率が悪い、おふざけ動画垂れ流しのほうが100倍楽。

市町村系とか

四国一周しました
房総一周しました
四国の話はこのブログの変な場所に、房総の話はここに書いてあります(ついでにここにテヘランの話も書いてあります)
やっぱり実際にいくと知見が広がるのでおすすめですが、四国は離島も大切なのでそこが難しいですね。今度は離島に行きたい。

DP未難25

まとめ

競技プログラミングに関しては、やっぱりゴールがないのがなあ。もう半ば引退ムードに近いのですが、この前やり残したこと(国内オンサイト優勝)があることに気づいて、企業コンテストは得意だしせめてそのぐらいは達成してから引退しようという気分です。
あとはまあ語学力と研究と市町村の3つなんですけど、語学力は相も変わらず speaking 力が足を引っ張りすぎていて解決策がなさすぎるのが困りもので (Discordチャンネルにもっと人が欲しいなぁ)、研究はまあ頑張る他なくて、残りは市町村ですかね。市町村に関しては、また未踏の地に行きたいですね。予定では来年の春には三陸に遊びに行く予定です。

*1:なっていません。IELTS 6.0です。

100年分の一次遷移の観察

この記事は 今年読んだ一番好きな論文2017 Advent Calendar 2017 - Adventar の13日目の記事です。

はじめに

こんにちは、tozangezanです。Twitter上で🐺でも学部生でも参加できるそうなので、参加してみることにしました。

このブログの他の記事をみればわかると思いますが、普段はプログラミングコンテストで時間を溶かしたり、各地に(コンテスト目的で)遊びに行ったりしています。ただ、僕の専攻は生態学なので*1、今回は生態学に関連する論文です。

論文の概要

Buma, B., et al. 2017. A foundation of ecology rediscovered: 100 years of succession on the William S. Cooper plots in Glacier Bay, Alaska. Ecology, 98(6), pp. 1513–1523.
です。内容を簡単に表すと、100年以上前に氷河が溶けた土地で植生がどう変化していくか(一次遷移)を数十年間調査していた調査区域を、このたび再発見したので追加で調査することで100年以上の一次遷移の観察結果を得ましたという話です。一次遷移を定点観測した研究の中では最長のものの一つとこの論文では主張しています。

遷移...?

まず、一次遷移という言葉がありますが、これは何でしょうか?
ここで扱う遷移というのは、ある土地における(しばしば年単位での)植物相の変化を言います。よく高校生物の教科書で例として登場するのは、火山活動によって地表が溶岩に覆われた場所から、コケ類などが他所から移入し、しばらく生えたり枯れたりすることで薄い土壌ができます。すると草本類が生育するようになり、より地中の栄養が増えていきます。栄養が増えることによって低木が成長できるようになり、しばしば新たに住み始めるようになった鳥などによって種子が運ばれるなどして、高木が成長可能になる土地となる、という数百年にわたる時間変化です。これは一次遷移です(下図を参照)。

https://media1.britannica.com/eb-media/97/95197-004-7F9B8F09.jpg
(https://www.britannica.com/science/ecological-succession より引用)

これはしばしば否定的な意見もありますが*2、どういう環境にどういう植物種が存在するかを考えるにおいては、非常に重要な傾向です*3

一方で、二次遷移というのもありますが、これは山火事が起こった後など、土壌が存在している状態から遷移が始まるパターンで、こちらは栄養分が十分であったり、土中にすでに種子が存在してたりするために、一次遷移よりも短時間で遷移が進みます。普通こちらの方が頻繁に見られます。

https://media1.britannica.com/eb-media/98/95198-004-2619E3FA.jpg
(https://www.britannica.com/science/ecological-succession より引用)

今回の研究は、一次遷移を追ったものなので、最初土壌は存在しません。生物種の移入と土壌を蓄積していくことによって、ようやく遷移が進みます。

どうやったらこんなこと調査できるのか?

上にも書いたように、遷移の研究というのは百年単位での時間を追う必要があります。「今から100年間調査できるわけねーだろ頭冷やせ」と思いますよね? しかし、上手いことして100年間使わずとも100年分の遷移結果を見ることはできます。

f:id:tozangezan:20171203174653p:plain

火山の噴火の時期とそれぞれの噴火においてどこが溶岩に覆われたかの記録があれば、異所的だが同時的に「噴火からの年数ー植生」の対応を見ることができ、長期間にわたる調査をしなくとも遷移の様子がわかるとされ、国内でも調査が行われてきました(参考: https://www.jstage.jst.go.jp/article/vegsci/29/2/29_KJ00008519581/_article/-char/ja/)。

しかし、この方法には問題点もあります。一つ目の問題点は、同所的でないために、環境の差、それも地形や水の量などが異なり、それぞれ別の環境に適応した種が入ってきている可能性があること。もう一つの問題点は、仮に全く同じ初期条件であったとしても、「どちらの種が先に移住してきたか」などによって行き着く先が大きく変わる可能性があることが挙げられます。下の図は、遷移そのものの図ではありませんが、わかりやすくたとえたイラストです。

f:id:tozangezan:20171206003242p:plain
上の図は、ある種が移入すると、その種が自種が有利なように環境を作り変えることにより、後から競合種が入れなくなるということ*4を、ゲームが好きな人(左)とパーティーが好きな人(右)のどちらかが偶然先にテナント募集の店に移入してくることにより、各自が好きなような店に作り変えることで、後から移入してきた人が定着できなくなる様子を示しています。

このような初期状態のランダムな微小差から数十年後の植物相に大きな違いを生む場合は、異所的で同時的な調査の結果をそのまま遷移の結果と解釈することは、不適当だと考えられます。優占種A→優占種B→優占種A→優占種Bという遷移を起こすと思われていたものは、偶々Aが優占した後そのままの場所と、偶々Bが優占した後そのままの場所を交互に見てしまっただけかもしれません。

こういった問題点を解決する方法として今回登場するのが、特に頭のいい方法とかではなく、時間の暴力です

調査地

Google マップ

このあたりに調査プロットが8個あります。周囲から到達が困難であるため、人間活動はほぼありません。

1916年にWilliam S. Cooperという人が9個のプロットを設定し(そのうち1個は侵食で消失しました)、その時は氷河が溶けて地面が露出してから17〜37年経っていました。Cooperはこのプロットで調査を続け、その後は彼の弟子であった Donald B. Lawrence が調査を続けていました。しかしながら、Lawrence の死後、後継者に引き継ぐことがかなわず、調査プロットの位置はわからなくなってしまいました! 今回の研究は、このプロットの箇所を見つけることから始め、後世に残すために記録すると同時に、植物群集の構成や土壌の物質構成を調査し、地形や地面が露出してからの年数がどのような影響を及ぼしているかを明らかにしようというものです。

調査結果

調査プロットを探し当てることに関しては、こんな感じだったようです。

After some difficulty, plots were redis- covered using the original compass directions from Cooper (1923),

お疲れ様です。ところでプロットの詳細な位置は、幸い石に色を塗って印をつけたようで、それで正確に再現ができたようです。よかったですね。
そして以下の画像が定点観測をした写真です。(1879年に氷河が解けた場所)

f:id:tozangezan:20171206225333p:plain

かなり綺麗に遷移が進んでいることを実感できる写真だと思います。それにしても一次遷移は最初の草地ができるまでが本当に長いですね。ここだけで70年近くかかっているのではないでしょうか。

という表面的な話はこのくらいにしておいて、実際に植生変化がどの程度起こったのか、定量的に見ていくとこのようになりました。

f:id:tozangezan:20171207000250p:plain

まず全体的な傾向から。今回の調査によって増えたのは右の3列、100年以降の点です。ここで明らかになったのは、100年以上経ったときは、もはや最初の数十年の差は重要ではないことです。
紫色の線について: 出現する総種数は、年を経るごとに大きくなっています。ただし、これだけで安易に生物多様性が上昇したとはいえません(後述)。
オレンジ色の線について: Salix(ヤナギ)の割合は、最初は(ないので)同じですが、ある程度以上に上昇すると、後に100年が経過しても割合が減少しません。その一方で、50~60年程度経過した時点でヤナギの割合があまり高くない場所では、植生遷移が進みSalixが他の種にとってかわられています。
緑色の線について: 遷移初期に生えるバラ科の草本やコケ類(、それからヤナギ)などの割合ですが、遷移が進むとともに減少していき、2016年になるとヤナギ以外は存在が確認されなくなっています。遷移初期にバラ科の草本やコケ類が多い場所では、ヤナギが生えにくいことにより、後々高木が優占する環境に遷移しやすいということです。

種多様性に関して

f:id:tozangezan:20171207003752p:plain:w300
いずれの数値も、高い方が種多様性が高いことを表しています。PIEは「ランダムに2個体とってきたとき、その2個体が違う種である確率」、ENSは「PIEが同じで、出現頻度が等しくなるように種を割り振るとしたら、何種が存在することになるか」という値です。
なぜ種数が増加しているのに多様性が減少しているのかというと、一部の優占種が増加する一方で、多くの種は希少で、限られた場所にしか生息していないことに因ります。

土壌の状態

時間が経つにつれ窒素量が増加するが、ヤナギが優占しているプロットにおいては窒素量が低く、結果としてC:N比が高くなる傾向にありました。

結論

数十年の差は、100年以上経つ頃にはほとんどなくなる。

おそらく、最初の方が植物の1世代が短いために違いが大きく見えるのですが、徐々に1世代の長い植物が優占するにつれ、小さな年数の違いが影響しなくなる、地理的な影響の方が大きい違いを起こしている、などに起因するものだと思われます。

ある時期の植生の違いが大きく影響している。これは地理的な違いが原因。

ヤナギ優占の領域では、その後の植物の置きかわりが少なく、ヤナギが優占したままです(ただし、このまま永遠にヤナギ優占かどうかはまだわかりません)。そうでない領域では、いずれトウヒ属が成長するということです。

これら2つの結果について、原因として考えられる環境の違いについても触れられています。これらは、種子の移入速度と、氷河の後退速度の関係が原因だと議論されています。この場所を優占しうる植物としては、もっとも遷移初期に現れる特性を持ったヤナギ属、比較的遷移初期に現れるハンノキ属、遷移後期に現れるトウヒ属の三つとされますが、ヤナギ属はこの中でももっとも種子が軽く移入率が高いです。氷河の後退速度がハンノキ属・トウヒ属の種子拡散速度よりも速い時は、余った場所にヤナギ属だけが入ることになり、結果的にヤナギ属の優占を引き起こします。ヤナギ属優占では、光制限などによって新たにトウヒ属などの実生が成長しにくいことにより、なかなか遷移が進みにくくなります。しかし氷河の後退速度が早くなければハンノキ属も移入が可能となり、ヤナギ属の優占が抑えられ、その後トウヒ属が現れることができます。

光制約によって成長の遅い遷移後期種が加入できない状況は、この「初期状態の小さな違いがしばらく経ったときに大きな違いになる」例だと考えられます。これは同時的で異所的な遷移の研究が検出できないことですね。priority effect (早い者勝ち) も光制約など、こういった原因によるものが多いのでしょう。

ヤナギ属の優占場所は、生産性が低い。

この違いを生み出しているのは、ハンノキ属のフランキア共生菌でしょうかね。国内でも窒素固定のためにハンノキ属を育てることも行われているくらい(参考)なので、これは生産性が高そうです。
これに対してヤナギ属は、窒素固定力が低く、ヤナギ属優占の場所では窒素量が他と比べて顕著に少ないです。

100年は短い。もっと長いスパンで調査する必要がある。

ヤナギ属が存在する状態では本当に永遠に他の植物が優占種にならないのか? トウヒ属が優占した後、トウヒ属にさらに取って代わる植物が存在するのか? 再び撹乱が起きたらどのような反応をするのか?
まだまだたくさん未解決の問いは残っていますし、一応世界一長期間にわたって調査されてきた一次遷移の調査プロットといわれています。どうか、これからも頑張ってください。

おわりに

卒論の提出期限が近いのに、一体何をやっていたのでしょう。しっかり論文を読み込んでから何を書くかピックアップして、他の研究をあさったりして、お絵描きをして、大変なアドベントカレンダー企画でした*5

研究といえば、最近研究室で料理をするのにハマっています。普段はうどんを茹でて気が向いた具材(スパゲッティのソースとか、お茶漬けとか、豆腐とか)とかを入れて美味しいか美味しくないかをやってるんですが、この前はすき焼きのタレを入手したので普通に鍋をやってみました。普通に美味しいです。生協で買うより安いのが重要。
研究室料理アドベントカレンダーとかがあったら面白いかもしれませんね。

f:id:tozangezan:20171211163742p:plain
*6
大変だったので賞ください(クズ)

*1:大概ここで驚かれます

*2:山火事や倒木や土砂崩れなどが、ランダムに起こることによって再びこの遷移の途中段階の場所を作り出すので、遷移というのは終わりがあるものではありませんし、このような途中段階に適応した植物というものが存在できるのは、こういう大小様々な撹乱のおかげです。

*3:理論系の研究をしているので数式で何かを説明されるのが好きなのですが、そういう人へのオススメとして、Michel Loreau の From Populations to Ecosystems Theoretical Foundations for a New Ecological Synthesis という本を挙げておきます。遷移の話は6章で扱われています。

*4:実はこういうことを数理モデルを用いることでも考えることができて、これは僕の研究テーマの一部です

*5:今までで一番大変だったアドベントカレンダー企画は去年のCompetitive Programming Advent Calendarで海外参加者とかを集めてみんなにインタビューして、それを記事にまとめたものでした。その次に大変だったのは一人で25記事書いたやつでした。

*6:The American Naturalistでよくある尺余りを絵で埋めるやつ