PKU演習問メモ(6/15)

No. 問題名 問題の種類および解法 難易度
3041 Asteroids 二部グラフの最小点カバー ★★★☆☆
2975 Nim ゲーム ★★☆☆☆
2917 Diophantus of Alexandria 不定方程式の解の個数 ★★☆☆☆

3041 Asteroids

問題概要

nxnのグリッドの中にk個の石があり、それぞれの座標が与えられる。
今、任意の1列のもしくは1行の石を全て消す操作が何回でも行えるとき、選択する列と行の個数の和を最小にして全ての石を消したい。そのような最小値を求めよ。

n≦500,k≦10000である。

解法

下のような例があるため、石が多い行、または列を選んでいくような解法は間違い。

XXX......
X..XX....
X....XX..
X......XX
.........
.........
.........
.........
.........

次のような二部グラフを考えたら上手くいった。

  • 列に対応するn個のノードを赤、行に対応するn個のノードを青にする
  • (x,y)の座標に石があるとき、赤のx番目のノードと青のy番目のノードをエッジで結ぶ

すると、「石を消す列または行を選ぶ」操作は、「エッジの少なくともどちらかのノードを選ぶ」操作に対応することがわかる。
石を消す列または行の数を最小にしたいのだから、この二部グラフの最小点カバーを求めればそれが求まることがわかる。

ソースコード
int n,k;
vi e[1000];
bool v[1000]; int p[1000];

bool match(int s)
{
	if(s<0)return 1;
	fr(it,e[s])if(!v[*it])
	{
		v[*it]=1;
		if(match(p[*it]))return p[s]=*it,p[*it]=s,1;
	}
	return 0;
}

int main()
{
	scanf("%d%d",&n,&k);
	rep(i,k)
	{
		int x,y; scanf("%d%d",&x,&y);
		x--; y+=500-1;
		e[x].pb(y),e[y].pb(x);
	}
	
	int ans=0;
	fill_n(p,1000,-1);
	rep(i,n)
	{
		fill_n(v,1000,0);
		if(match(i))ans++;
	}
	cout<<ans<<endl;
	
	return 0;
}

2917 Nim

問題概要

Nim(石取りゲーム)において"losing position"とはそこから相手が最善を尽くした場合、自分がどう手を選んでも勝てない状態のことをいい、"winning position"は逆に、次の相手の手番を"losing position"の状態で渡せることを言う。


Nimにおいて有名な定理として、各山の個数のxorをとった時に0になればその状態は"losing position"であることが知られている。


今、山の状態が与えられたとき、losing positionにするような石の取り除き方は何通りあるか。
山の数nは1000以下、各山の大きさk[i]は10^9以下である。

解法

xorを二度とると0になることを利用する。
すなわち、全体のxorを求めておき、それぞれの山について

  • 一度その山の個数のxorを取り、xorを消す
  • 新たなその山の個数として考えられるのは0からk[i]-1である
  • この中に残りのxorに等しいものがあれば、そのような数になるよう石を取ることでlosing positionにできる。


これを実装したのが以下のコード。

ソースコード
int main()
{
  int n;
  while(cin>>n,n){
    ll pile[1000],sum=0;
    rep(i,n)cin>>pile[i],sum^=pile[i];
    
    int ans=0;
    rep(i,n)if((sum^pile[i])<pile[i])ans++;
    cout<<ans<<endl;
  }
  return 0;
}

2917 iophantus of Alexandria

問題概要

n≤10^9なるnが与えられるとき、
1/x + 1/y = 1/nを満たす自然数の組(順序は考慮しない)の数を求めよ。

解法

x≤yとすると、xはn<x≤2*nでなくてはならないことはすぐわかる。
式変形してy=nx/(x-n)であるが、ここでx-n=tと置くと、0<t≤nかつ
y=n^2/t + nである。


yが整数になるのはn^2/tが整数になるときであるので、0<p≤nを満たすようなn^2の約数の個数を求めればそれが解である。
n^2の約数の個数は素因数分解により求められる。このうち0<p≤nの個数aは、
「n^2の約数においてn以外のもの」にはn^2/pとpに一対一の対応があることから(a+1)/2の式で求められる。

ソースコード
int main()
{
  int CS,cs; cin>>CS;
  rep(cs,CS){
    cout<<"Scenario #"<<cs+1<<":"<<endl;
    int n,cnt[40]={0},nc=0; cin>>n;
    for(int i=2;i*i<=n;i++)if(n%i==0){
	while(n%i==0)cnt[nc]++,n/=i;
	nc++;
      }
    if(n>1)cnt[nc++]=1;

    ll ans=1;
    rep(i,nc)ans*=1+cnt[i]*2;
    ans++; ans/=2;
    cout<<ans<<endl<<endl;
  }
  return 0;
}