2004上海区域赛 Asia Regional Shanghai 题解 (7/10)
第一次发题解瑟瑟发抖 Σ(゚д゚;)
HDU 1052 Tian Ji -- The Horse Racing
题意简述:田忌赛马,给出双方马的速度,赢一局得200铜钱,求最多得多少铜钱。
一开始按照题意想的二分图,后来发现是贪心 Σ(゚д゚;)
先把国王的马和田忌的马排序,塞进一个deque里
然后按照以下方法操作:
(1) 若田忌最快的马比国王最快的马快,则用田忌最快的马赢国王最快的马。
设国王最快的马为 k ,田忌最快的马为 t ,且各有另一只马 k', t'
有 k' ≤ k < t , t' ≤ t
如果让 k 和 t' 比,k' 和 t 比
a. t' ≤ k' 时,原来一胜一负/平,交换后一胜一负。
b. k' < t' ≤ k 时,原来两胜,交换后一胜一负/平。
c. t' > k 时,原来两胜,交换后两胜。
因此交换不会使结果更优,得证。
(2) 若田忌最快的马比国王最快的马慢,则用田忌最慢的马输国王最快的马。
因为无论用什么马跟国王的这只马比都会输,因此消耗掉己方最慢的马最优。
(3) 若田忌最快的马和国王的马一样快,且田忌最慢的马比国王最慢的马快,则用田忌最慢的马赢国王最慢的马。
设国王最快的马为 k ,田忌最快的马为 t ,且各有另一只马 k', t'
有 k < t ≤ t' , k ≤ k'
如果让 k 和 t' 比,k' 和 t 比
a. k' ≤ t 时,原来两胜,交换后一胜一平。
b. t < k' ≤ t' 时,原来两胜/一胜一平,交换后一胜一负
c. k' > t 时,原来一胜一负,交换后一胜一负
因此交换不会使结果更优,得证。
(4) 若田忌最快的马和国王的马一样快,且田忌最慢的马比国王最慢的马慢或一样快,则用田忌最慢的马输国王最快的马。
设国王最快的马为 k ,田忌最慢的马为 t ,且各有另一只马 k', t'
有 t ≤ k' ≤ k , t ≤ t' ≤ k
如果让 k 和 t' 比,k' 和 t 比
a. k' ≤ t' 时,原来两负或一负一平,交换后两负。
c. k' > t' 时,原来一胜一负,交换后两负。
因此交换不会使结果更优,得证。
其实以上的证明并不严格,因为除了交换还能三轮换、四轮换,可能那样会使得结果更优,但是emmm... 这个结论是正确的(逃
时间复杂度O(n)
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; int king[10010],tj[10010],ans=0; void comp(int t,int k){ if(t<k) ans--; else if(t>k) ans++; } int cmp(int a,int b){ return a>b; } int main(){ int n,tfp,tep,kfp,kep; while(scanf("%d",&n)!=EOF&&n!=0){ for(int i=0;i<n;i++){ scanf("%d",&tj[i]); } for(int i=0;i<n;i++){ scanf("%d",&king[i]); } sort(tj,tj+n,cmp); sort(king,king+n,cmp); tfp=kfp=ans=0; tep=kep=n-1; for(int i=0;i<n;i++){ if(tj[tfp]<king[kfp]) {comp(tj[tep],king[kfp]); --tep,++kfp;} else if(tj[tfp]>king[kfp]) {comp(tj[tfp],king[kfp]); ++tfp,++kfp;} else if(tj[tep]>king[kep]) {comp(tj[tep],king[kep]); --tep,--kep;} else {comp(tj[tep],king[kfp]); --tep,++kfp;} } printf("%d\n",ans*200); } return 0; }
HDU 1661 Amphiphilic Carbon Molecules
看这个题只想到 O(n^3) 的做法,然后发现紫书上有这个题 QAQ
直接暴力方法是枚举所有的点对,判断所有点在线的哪边,枚举点对 O(n^2),判断所有点的位置 O(n),总共 O(n^3),对于1000的数据显然不能过
而扫描线法是枚举每一个点,按照极角将其他的点排序,让扫描线旋转一圈,每次碰到点的时候,让上一个点到线的另一侧,,一边分子数+1,另一边分子数-1,复杂度降为O(n^2*log n)
另外还有一个技巧:对于某一个固定的转轴,一点等价于与该点关于转轴中心对称的,极性与该点相反的点,因此可以把所有的纵坐标变成大于0的,可以简化一些代码。
#include <cstdio> #include <algorithm> //#include <cstring> using namespace std; typedef long long ll; struct point { int x,y,pol; void invert(){ x=-x,y=-y,pol=1-pol; } } arr[1010],tmp[1010]; int tcnt[2],ncnt[2],olcnt[2]; bool cmp(point a,point b){ if(a.x==0) return b.x<0; if(b.x==0) return a.x>0; if(a.x*b.x<0) return a.x>b.x; return a.y*b.x<b.y*a.x; } inline bool notequal(point a,point b){ return cmp(a,b)||cmp(b,a); } int main(){ int n,ptr,ans,ovl; while(scanf("%d",&n)!=EOF&&n!=0){ ans=0; for(int i=0;i<n;i++){ scanf("%d%d%d",&arr[i].x,&arr[i].y,&arr[i].pol); } for(int i=0;i<n;i++){ ptr=ovl=0; tcnt[0]=tcnt[1]=olcnt[0]=olcnt[1]=0; for(int j=0;j<n;j++){ if(arr[j].x==arr[i].x&&arr[j].y==arr[i].y){ ++ovl; continue; } tmp[ptr].x=arr[j].x-arr[i].x; tmp[ptr].y=arr[j].y-arr[i].y; tmp[ptr].pol=arr[j].pol; if(tmp[ptr].y<0||(tmp[ptr].y==0&&tmp[ptr].x<0)) tmp[ptr].invert(); ++tcnt[tmp[ptr].pol]; ++ptr; } ncnt[0]=tcnt[0]; ncnt[1]=tcnt[1]; sort(tmp,tmp+n-1,cmp); for(int j=0;j<n-1;j++){ if(j!=0&¬equal(tmp[j],tmp[j-1])){ ans=max(ans,max(tcnt[0]-ncnt[0]+ncnt[1]+olcnt[1],tcnt[1]-ncnt[1]+ncnt[0]+olcnt[0])+ovl); olcnt[0]=olcnt[1]=0; } --ncnt[tmp[j].pol]; ++olcnt[tmp[j].pol]; } ans=max(ans,max(tcnt[0]-ncnt[0]+ncnt[1]+olcnt[1],tcnt[1]-ncnt[1]+ncnt[0]+olcnt[0])+ovl); } printf("%d\n",ans); } return 0; }
HDU 1662 Link and Pop -- the Block Game
题意简述:连连看,每个块在每次操作后会向指定的方向移动,模拟玩家的操作。
基本算是模拟题。。。慢慢写吧
判断两个块是否可以连接有 O(n*m) 的方法,开一个vis数组,把两者可以直线到达的位置打上标记。
第一块打标记时顺便看找不找得到第二块,找得到就说明可以直线相连
第二块打标记时找第一块的标记,找得到说明可以两条线相连
然后每行每列扫一遍,如果发现有两个标记可以直线到达则说明可以三条线相连
坑点:线可以出界,也就是说像
1 3 AS BS AS
这样的数据里面两个A可以相连
时间复杂度应该是 O((nm)^3)
#include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; typedef long long ll; const int dirs[5][2]={ {0,0},{-1,0},{1,0},{0,-1},{0,1} }; int n,m,clk,minlc,mxi1,mxj1,mxi2,mxj2; struct block { char name; int dir,updt,id; block(){name=dir=0;updt=-1;} } arr[33][33]; int vis[33][33]; int loci[1000],locj[1000]; vector<int> ids[128]; bool isavail(int i,int j){ return i>=0&&i<n&&j>=0&&j<m&&arr[i][j].updt==-1; } bool mkmove(int i,int j){ if(arr[i][j].updt==clk) return false; if(arr[i][j].updt==-1) return false; int ni=i+dirs[arr[i][j].dir][0]; int nj=j+dirs[arr[i][j].dir][1]; if(isavail(ni,nj)){ arr[ni][nj]=arr[i][j]; arr[ni][nj].updt=clk; arr[i][j].updt=-1; loci[arr[ni][nj].id]=ni; locj[arr[ni][nj].id]=nj; return true; } return false; } int getdir(char ch){ switch(ch){ case 'U': return 1; case 'D': return 2; case 'L': return 3; case 'R': return 4; default: return 0; } } bool expand(int ii,int jj,int f,int v){ for(int i=ii+1;i<n+2;i++){ if(vis[i][jj]==f) return true; if(i!=0&&i!=n+1&&arr[i-1][jj-1].updt!=-1) break; vis[i][jj]=v; } for(int i=ii-1;i>=0;i--){ if(vis[i][jj]==f) return true; if(i!=0&&i!=n+1&&arr[i-1][jj-1].updt!=-1) break; vis[i][jj]=v; } for(int j=jj+1;j<m+2;j++){ if(vis[ii][j]==f) return true; if(j!=0&&j!=m+1&&arr[ii-1][j-1].updt!=-1) break; vis[ii][j]=v; } for(int j=jj-1;j>=0;j--){ if(vis[ii][j]==f) return true; if(j!=0&&j!=m+1&&arr[ii-1][j-1].updt!=-1) break; vis[ii][j]=v; } return false; } int reachable(int i,int j,int ti,int tj){ ++i,++j,++ti,++tj; if(arr[ti-1][tj-1].updt==-1) return 0; memset(vis,0,sizeof(vis)); vis[ti][tj]=1; if(expand(i,j,1,2)) return 1; if(minlc==2) return 0; if(expand(ti,tj,2,3)) return 2; if(minlc==3) return 0; vis[ti][tj]=0; int ssign; for(int i=0;i<n+2;i++){ ssign=0; for(int j=0;j<m+2;j++){ if(i!=0&&j!=0&&i!=n+1&&j!=m+1&&arr[i-1][j-1].updt!=-1) ssign=0; if(vis[i][j]==2){ if(ssign==0) ssign=2; else if(ssign==3) return 3; } else if(vis[i][j]==3) { if(ssign==0) ssign=3; else if(ssign==2) return 3; } } } for(int j=0;j<m+2;j++){ ssign=0; for(int i=0;i<n+2;i++){ if(i!=0&&j!=0&&i!=n+1&&j!=m+1&&arr[i-1][j-1].updt!=-1) ssign=0; if(vis[i][j]==2){ if(ssign==0) ssign=2; else if(ssign==3) return 3; } else if(vis[i][j]==3) { if(ssign==0) ssign=3; else if(ssign==2) return 3; } } } return 0; } char buf[10]; void dispose(int i,int j){ arr[i][j].updt=-1; loci[arr[i][j].id]=-1; } bool bigger(int i1,int j1,int i2,int j2){ return i1==i2?j1>j2:i1>i2; } void print(){ for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ printf("%c",arr[i][j].updt==-1?'.':arr[i][j].name); } printf("\n"); } } int main(){ int nid,ct=0; bool flag; while(scanf("%d%d",&n,&m)==2&&n!=0){ nid=clk=0; for(int i=0;i<128;i++) ids[i].clear(); for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ scanf("%s",buf); arr[i][j].name=buf[0]; arr[i][j].dir=getdir(buf[1]); arr[i][j].id=nid; arr[i][j].updt=0; loci[nid]=i; locj[nid]=j; ids[buf[0]].push_back(nid); ++nid; } } minlc=0; while(true){ minlc=4; for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(arr[i][j].updt==-1) continue; int idsl=ids[arr[i][j].name].size(); int tminlc=-1; for(int k=0;k<idsl;k++){ if(ids[arr[i][j].name][k]==arr[i][j].id) continue; if(loci[ids[arr[i][j].name][k]]==-1) continue; int ni=loci[ids[arr[i][j].name][k]]; int nj=locj[ids[arr[i][j].name][k]]; if(bigger(i,j,ni,nj)) continue; int nowlc=reachable(i,j,ni,nj); if(nowlc&&(tminlc==-1||nowlc<tminlc||(nowlc==tminlc&&bigger(mxi2,mxj2,ni,nj)))){ mxi1=i; mxj1=j; mxi2=ni; mxj2=nj; minlc=nowlc+1; tminlc=nowlc; } } if(tminlc!=-1) minlc=tminlc; if(minlc==1) goto goto_break; } } goto_break:; if(minlc==4) break; dispose(mxi1,mxj1); dispose(mxi2,mxj2); //print(); //printf("\n"); flag=true; while(flag){ flag=false; ++clk; for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(mkmove(i,j)) flag=true; } } } //print(); //printf("\n\n"); } printf("Case %d\n",++ct); print(); } return 0; }
HDU 1663 The Counting Problem
题意简述:求从m到n中0-9各出现了多少次。
简单的数位dp,dp[i]表示i位数中1-9的个数以及带前导零的i位数中0的个数,dp0[i]表示不带前导零的i位数中0的个数,转移方程
dp[i]=dp[i-1]*10+10^(i-1)
dp0[i]=dp0[i-1]+dp[i-1]*9
时间复杂度大概是O(log(n)+ log(m))
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; int dp[10],dp0[10],ans1[10],ans2[10],temp[10]; int pow(int a,int n){ ll ans=1; for(int i=0;i<n;i++) ans*=a; return ans; } void init(){ for(int i=1;i<10;i++){ dp[i]=dp[i-1]*10+pow(10,i-1); dp0[i]=dp0[i-1]+dp[i-1]*9; } } void solve(int n,int *arr){ int dptr=1,cnt=0,flag=1; while(n/dptr>=10){ ++cnt; dptr*=10; } arr[0]=dp0[cnt]; for(int j=1;j<10;j++) arr[j]=dp[cnt]; while(dptr){ for(int i=flag;i<n/dptr;i++){ arr[i]+=dptr; for(int i=0;i<10;i++) arr[i]+=dp[cnt]; } flag=0; arr[n/dptr]+=n%dptr; n%=dptr; dptr/=10; --cnt; } } int main(){ init(); int a,b; while(scanf("%d%d",&a,&b)==2&&a!=0){ if(a>b) swap(a,b); solve(b+1,ans1); solve(a,ans2); for(int i=0;i<10;i++) printf("%d%c",ans1[i]-ans2[i],i==9?'\n':' '); } return 0; }
HDU 1664 Different Digits
题意简述:给出一个数,求它的一个倍数,使得其各位数字的种类最少的前提下最小。
首先,可以证明,任何一个数都有不超过两种数字组成的答案,因为对任意n,对于集合 {1,11,111,...,(10^(n+1)-1)/9} 中的 n+1 个数,其中一定存在两个数 n1 n2 (n1 < n2) 模 n 余数相等,于是 n2 - n1 只由 0 和 1 组成,且为n的倍数,得证。
所以先看有没有只用1种数字组成的答案,再遍历所有的数字组合可能,bfs搜索余数,记录下搜索的路径,然后比较搜出的结果大小就ok了。
每轮bfs最多搜n步,因此时间复杂度O(n)。
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> using namespace std; typedef long long ll; bool vis[100010]; int minpath[100010],dep[100010],patht[100010]; char str[100010],ans[100010]; int countdigit(int n){ int dn=0,ans=0; while(n){ dn|=1<<n%10; n/=10; } while(dn){ if(dn&1) ++ans; dn>>=1; } return ans; } int main(){ int n,t,depth,nmod,nnum,mnlen; queue<int> q; while(scanf("%d",&n)==1&&n!=0){ t=countdigit(n); if(t==1){ printf("%d\n",n); continue; } mnlen=0x3f3f3f3f; for(int i=1;i<10;i++){ memset(vis,0,sizeof(vis)); depth=nmod=0; while(!vis[nmod]&&depth<mnlen-1){ vis[nmod]=true; nmod=(nmod*10+i)%n; ++depth; } if(nmod==0){ mnlen=depth; nnum=i; } } if(mnlen!=0x3f3f3f3f){ for(int j=0;j<mnlen;j++) printf("%d",nnum); printf("\n"); goto nextloop; } if(t==2){ printf("%d\n",n); continue; } mnlen=0x3f3f3f3f; for(int i=0;i<10;i++){ for(int j=i+1;j<10;j++){ memset(vis,0,sizeof(vis)); q=queue<int>(); depth=1; if(i!=0){ q.push(i); vis[i]=true; minpath[i]=0; patht[i]=i; dep[i]=0; } q.push(j); vis[j]=true; minpath[j]=0; patht[j]=j; dep[j]=0; q.push(-1); while(!vis[0]){ t=q.front(); q.pop(); if(q.empty()) break; if(t==-1){ q.push(-1); ++depth; if(depth>mnlen) break; } else { int t1=(t*10+i)%n,t2=(t*10+j)%n; if(!vis[t1]){ vis[t1]=true; minpath[(t*10+i)%n]=t; dep[t1]=depth; patht[t1]=i; q.push((t*10+i)%n); } if(!vis[t2]){ vis[t2]=true; minpath[(t*10+j)%n]=t; dep[t2]=depth; patht[t2]=j; q.push((t*10+j)%n); } } } if(vis[0]){ int ptr=0; do { str[dep[ptr]]=patht[ptr]+'0'; ptr=minpath[ptr]; } while(ptr); str[dep[0]+1]='\0'; if(dep[0]<mnlen){ strcpy(ans,str); mnlen=dep[0]; } else { if(strcmp(ans,str)>0){ strcpy(ans,str); } } } } } printf("%s\n",ans); nextloop:; } return 0; }
HDU 1667 The Rotation Game
题意简述:不好概括。。。看原题吧
用迭代加深搜索 IDDFS 可解,由于一步最多只能向中心移入一个目标数字,所以搜索剩余步数小于中心非目标数字块的数量时剪枝。
用常量数组可以大幅简化代码,还有注意要找字典序最小的,所以搜到结果不要马上退出,要再看其他的数字会不会有字典序更小的。
时间复杂度是 O(8^n) , n 是答案也就是搜索深度,看起来很大但是有剪枝所以还是能过
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; typedef long long ll; int arr[24],route[100],route2[100]; const int rots[8][7]={ {0,2,6,11,15,20,22}, {1,3,8,12,17,21,23}, {10,9,8,7,6,5,4}, {19,18,17,16,15,14,13}, {23,21,17,12,8,3,1}, {22,20,15,11,6,2,0}, {13,14,15,16,17,18,19}, {4,5,6,7,8,9,10}, }; const int rev[8]={5,4,7,6,1,0,3,2}; const int ctr[8]={6,7,8,11,12,15,16,17}; void mkmove(int n){ int temp=arr[rots[n][0]]; for(int i=1;i<7;i++){ arr[rots[n][i-1]]=arr[rots[n][i]]; } arr[rots[n][6]]=temp; } int countctr(int n){ int ans=0; for(int i=0;i<8;i++) if(arr[ctr[i]]==n) ++ans; return ans; } void print(){ printf(" %d %d \n",arr[0],arr[1]); printf(" %d %d \n",arr[2],arr[3]); for(int i=4;i<11;i++) printf("%d",arr[i]); printf("\n %d %d \n",arr[11],arr[12]); for(int i=13;i<20;i++) printf("%d",arr[i]); printf("\n %d %d \n",arr[20],arr[21]); printf(" %d %d \n\n",arr[22],arr[23]); } bool iddfs(int n,int dep,int limit){ int cnt=countctr(n); bool flag=false; if(cnt==8) return true; if(8-cnt>limit-dep) return false; for(int i=0;i<8;i++){ route[dep]=i; mkmove(i); if(iddfs(n,dep+1,limit)) flag=true; mkmove(rev[i]); if(flag) return true; } return false; } bool cmproute(int l){ for(int i=0;i<l;i++){ if(route[i]<route2[i]) return true; if(route[i]>route2[i]) return false; } return false; } int main(){ int ans=-1,ansn; while(scanf("%d",&arr[0])!=EOF&&arr[0]!=0){ ans=-1; for(int i=1;i<24;i++){ scanf("%d",&arr[i]); } //print(); for(int j=0;;j++){ for(int k=1;k<4;k++){ //printf("%d %d\n",k,j); if(iddfs(k,0,j)){ if(ans==-1){ //printf("ok!\n"); memcpy(route2,route,sizeof(route)); ans=j; ansn=k; } else { //printf("try replace\n"); if(cmproute(ans)){ memcpy(route2,route,sizeof(route)); ansn=k; //printf("ok!\n"); } } } } if(ans!=-1) break; } for(int i=0;i<ans;i++) printf("%c",route2[i]+'A'); if(ans==0) printf("No moves needed"); printf("\n%d\n",ansn); } return 0; }
时限15000ms,这个代码327ms
HDU 1669 Jamie's Contact Groups
二分答案,然后做一对多的二分图多重匹配,写法和匈牙利算法相近,就是每一个电话簿都需要遍历其中所有元素找经过它们的增广路。
时间复杂度 O(mnlog(n))
#include <iostream> #include <string> #include <sstream> #include <algorithm> #include <vector> #include <cstring> using namespace std; int n,m,limit; bool vis[510]; int cont[510][1010]; vector<int> g[1010]; bool dfs(int k){ for(auto i:g[k]){ if(vis[i]) continue; vis[i]=true; if(cont[i][0]<limit){ cont[i][++cont[i][0]]=k; return true; } else { for(int j=1;j<=limit;j++){ if(dfs(cont[i][j])){ cont[i][j]=k; return true; } } } } return false; } bool check(){ for(int i=0;i<n;i++){ memset(vis,0,sizeof(vis)); if(!dfs(i)) return false; } return true; } int main(){ int t; string buf,name; while(cin>>n>>m&&n!=0){ getline(cin,buf); for(int i=0;i<n;i++){ getline(cin,buf); istringstream sin(buf); sin>>name; g[i].clear(); while(sin>>t){ g[i].push_back(t); } } int ansl=1,ansr=n; while(ansl<ansr){ memset(cont,0,sizeof(cont)); limit=(ansl+ansr)/2; if(check()) ansr=limit; else ansl=limit+1; } cout<<ansl<<'\n'; } return 0; }

浙公网安备 33010602011771号