PKU演習問メモ(8/22)
集中できないお……orz
No. | 問題名 | 問題の種類および解法 | 難易度 |
---|---|---|---|
2115 | C Looooops | 合同式の割り算 | ★★★☆☆ |
2153 | Rank List | バケットソート | ★★★☆☆ |
2299 | Ultra-QuickSort | バブルソートの交換回数 | ★★★★☆ |
2007 | Scrambled Polygon | 幾何・凸図形の性質 | ★★☆☆☆ |
2059 | Watchdog | 実装 | ★★☆☆☆ |
2115 C Looooops
問題概要
for(variable i=A;i!=B;i+=C)というループの実行回数を求めよ。
ただしvariableはkビットの符号なし整数型である。
k≦32,
A,B,C<2^kを満たす。
解法
kがそこそこ大きいのでループをシミュレーションするとTLEになる。
A+xC≡B (mod 2^k)となるようなxを直接求める必要がある。
上の式を変形して、
xC≡B-A
この両辺をCで割りたい。
2^kとC,B-Aが互いに素の場合はmod2^kにおけるCの逆元をかければよい。
そうでない場合、元の方程式を考えると、
xC=B-A+y*2^kなので、両辺をGCD(C,B-A,2^k)で割れば、上の場合に帰着し、Cの逆元が求まる。
ソースコード
spaghetti sourceの法の逆元のコードを使用(その部分は略)
int main() { int a,b,c,k; while(scanf("%d%d%d%d",&a,&b,&c,&k),a||b||c||k) { if(a==b)puts("0"); else { ll K=1LL<<k; ll A=(b-a+K)%K,g=__gcd(A,K); g=__gcd(g,(ll)c); A/=g; c/=g; K/=g; ll ci=invMod(c,K); if(ci==0)puts("FOREVER"); else printf("%lld\n",ci*A%K); } } return 0; }
2153 Rank List
問題概要
n人がm回試験を行った成績が与えられる。
1〜m回目の試験終了時点での"Li Ming"の(その時点での合計得点に基づく)順位を出力せよ。
n≦10000,m≦50,
各テストの点数は0以上100以下の整数である。
解法
昔TLE出して放置した問題。
テストの点数は限られた範囲の整数なのでバケットソートができる。
バケットソートをした後、自分より上に居る人の人数を合計すれば自分の順位がわかる。
(点数は最大5000なので、これは普通に足し算すればよい)
入力がばらばら+文字列なので面倒。一度stable_sortで並べ替える。
文字列にstringを使ったりすると多分TLE?(多分昔の回答はバケットソート使ってなかったのも悪いと思うんだけど)
ソースコード
struct S{ char s[31]; bool operator<(const S &r)const{ return strcmp(s,r.s)<0; } }; struct cmp{ bool operator()(const pi &l,const pi &r)const{ return l.first<r.first; } }; int n,m,nm; pi in[500000]; int dist[5001],sc[10000]; int main() { scanf("%d ",&n); map<S,int> id; int li; S tmp; rep(i,n) { gets(tmp.s); id[tmp]=i; if(strcmp(tmp.s,"Li Ming")==0)li=i; } scanf("%d",&m); nm=n*m; rep(i,nm) { int p; scanf("%d ",&p); gets(tmp.s); in[i]=mp(id[tmp],p); } stable_sort(in,in+nm,cmp()); int mx=0; rep(i,m) { rep(j,n) { sc[j]+=in[m*j+i].second; mx=max(mx,sc[j]); } rep(j,mx+1)dist[j]=0; rep(j,n)dist[sc[j]]++; int cnt=1; for(int j=mx;j>sc[li];j--)cnt+=dist[j]; printf("%d\n",cnt); } return 0; }
2299 Ultra-QuickSort
解法
spaghetti sourceのバブルソートの交換回数のページ(http://www.prefield.com/algorithm/sort/mergecount.html)に解説がある。
バブルソートの交換回数は転置数を数えればよいのだが、O(n^2)ではなくO(nlog n)でマージソートをしながら、次のようにして可能。
- 左半分、右半分それぞれで転置数をカウント。
- マージするときに、右半分の要素が前にきたら、左半分で「残りの数」(こいつらは全て今マージしてる右の数より大きい)を転置数に足す
なるほどー。
ちなみに答えはlong longにしないと桁があふれるので注意。
ソースコード
ll rec(vi &a) { ll ret=0; int n=a.size(); if(n>1) { vi b(a.begin(),a.begin()+n/2); vi c(a.begin()+n/2,a.end()); ret+=rec(b)+rec(c); for(int i=0,j=0,k=0;i<n;i++) { if(k==c.size())a[i]=b[j++]; else if(j==b.size())a[i]=c[k++]; else if(b[j]<=c[k])a[i]=b[j++]; else a[i]=c[k++],ret+=n/2-j; } } return ret; } int main() { int n; while(scanf("%d",&n),n) { vi a; int t; rep(i,n)scanf("%d",&t),a.pb(t); cout<<rec(a)<<endl; } return 0; }
2007 Scrambled Polygon
問題概要
凸な多角形とは図形内の任意の2点を結んだ線分が、常にその図形の内部に含まれるような多角形のことをいう。
このような図形を頂点の一つを座標平面の原点に置いたとき、次のような性質が満たされる。
- 図形の頂点を含む象限は3つ以下である
- 図形を(0,0)のものから順に頂点を辿るとき、各象限内において、原点と頂点を結んだ直線の傾きは単調増加または単調減少である
いま、一点を原点にもつ凸多角形が各頂点の座標により与えられる。
多角形の頂点を、原点を出発し、反時計回りに頂点をめぐるような順序に並べ替えよ。
座標軸上に頂点はないものとする。
解法
問題文で丁寧に説明されている性質をそのまま使えばよい。
すなわち、象限それぞれで点を(原点からの)傾きの順にソートし、
最初の象限(点が含まれない象限の次)から出発して、象限を反時計回りに辿ればよい。
言葉では解りにくい気がするのでその場合はソース参照。。。
ソースコード
bool cmp(pi a,pi b) { double m=a.second*1.0/a.first,mm=b.second*1.0/b.first; return m<mm; } int main() { vector<pi> one,two,three,four,ans; int x,y; while(~scanf("%d%d",&x,&y)) { if(x<0&&y>0)one.pb(mp(x,y)); if(x<0&&y<0)two.pb(mp(x,y)); if(x>0&&y<0)three.pb(mp(x,y)); if(x>0&&y>0)four.pb(mp(x,y)); } sort(all(one),cmp); sort(all(two),cmp); sort(all(three),cmp); sort(all(four),cmp); ans.pb(mp(0,0)); if(one.empty()) { fr(i,two)ans.pb(*i); fr(i,three)ans.pb(*i); fr(i,four)ans.pb(*i); } else if(two.empty()) { fr(i,three)ans.pb(*i); fr(i,four)ans.pb(*i); fr(i,one)ans.pb(*i); } else if(three.empty()) { fr(i,four)ans.pb(*i); fr(i,one)ans.pb(*i); fr(i,two)ans.pb(*i); } else { fr(i,one)ans.pb(*i); fr(i,two)ans.pb(*i); fr(i,three)ans.pb(*i); } fr(i,ans)printf("(%d,%d)\n",i->first,i->second); return 0; }
2059 Watchdog
問題概要
一辺がsで各辺が座標軸に平行な正方形をした建物の屋上に、n個のハッチがある。
建物の左下の座標は(0,0)であり右上の座標は(s,s)である。
屋上に置く番犬をつなぐ杭を立てる場所を、以下のように求めよ。
- x座標、y座標がともに整数
- 番犬から最も遠いハッチまでの距離が、最も近い屋上のふちまでの距離を超えない
- 候補が複数ある場合x座標が最も小さいもの(それも複数ある場合はy座標が最小のもの)
そのような候補がない場合"poodle"と出力せよ。
s≦40,n≦50を満たす。
解法
正方形内の格子点について、条件を満たすかどうか全部試す。
ソースコード
int s,n; int X[50],Y[50]; bool ng[41][41]; bool ok(int y,int x) { int mne=min(min(y,s-y),min(x,s-x)); rep(i,n)if(mne<hypot(X[i]-x,Y[i]-y))return 0; return 1; } int main() { int cs; scanf("%d",&cs); while(cs--) { scanf("%d%d",&s,&n); rep(i,s+1)rep(j,s+1)ng[i][j]=0; rep(i,n)scanf("%d%d",X+i,Y+i),ng[Y[i]][X[i]]=1; int x=-1,y=-1; rep(j,s+1)rep(i,s+1)if(!ng[i][j]&&ok(i,j)) { y=i,x=j; goto END; } END: if(x<0)puts("poodle"); else printf("%d %d\n",x,y); } return 0; }