这两天木有一直做题,给我的Defy刷了MIUI,玩机去了...话说MIUI还真不错..
言归正传,前两天和队友在HDU上挂了华中北赛区的决赛题目,完全被虐啊,根本不在状态,希望比赛的时候千万不要出 现这种状况了...收起受伤的心灵,继续努力做题,提高自己吧,毕竟起步太晚,想一步登天是不可能的,一步一步来吧..
今天晚上终于把USACO第四章的题目做完了..考虑要不要停一段时间在做,因为现在的题目已经超出了我的水平, 很难自己想出来,这样难以达到好的做题效果,另外,也该开始回顾一下以前做过的题目和代码了,温故知新,很多东西都 有点忘了.
HDU 1010Tempter of the Bone 搜索+剪枝
二维迷宫,给定起点和终点,以及墙的位置,问能否在T时间时到达终点,不能走重复的路,给出的迷宫0<m,n<8 T<=50
搜索自然会超时,经过不断的剪枝,终于把这题优化到了0MS,一个BFS加两个DFS,这题是一道搜索的好题
剪枝一:如果所有能走的部分小于T,必然不能走完
剪枝二:奇偶性,从一个格子到另一个格子的步数的奇偶性是固定的,如果步数和时间奇偶性不同,也必然不能到达..只需要判断一次就可以了,如果步数和剩余时间同奇,那么走一步之后就是同偶了..另外有不少人说这是强力剪枝,其实不要这个剪枝也是0MS AC的...
剪枝三:最短路径,先从终点做一遍BFS,找到所有点到终点的最短路,之后DFS时如果剩下的时间T大于这点到终点的最短路,就不需要继续拓展这个点了,加上这个剪枝基本就能AC了,600MS+..
剪枝四:比较给力的一个剪枝,加上之后就0MS了..搜索到一个点后,对它接下来能走的块进行搜索,记录最大值,以及判断终点是否在这个块中,如果最大值小于剩余时间T,或者终点不在这个块中,也就不需要继续拓展了
#include <cstdio> #include <queue> using namespace std; int n,m,t; char smaze[10][10]; int vis[10][10],maze[10][10],vis2[10][10]; int str,stc,enr,enc,canz,canz2,hzd; queue<pair<int,int> > q; int dr[]={1,-1,0,0},dc[]={0,0,1,-1}; void bfs(int len,int step){ int s2=0; for(int i=0;i<len;i++){ int olr=q.front().first; int olc=q.front().second; q.pop(); for(int i=0;i<4;i++){ int nr=olr+dr[i],nc=olc+dc[i]; if(nr>=1&&nr<=n&&nc>=1&&nc<=m&&!vis[nr][nc]&&smaze[nr][nc]!='X'){ canz++; vis[nr][nc]=1; maze[nr][nc]=step; q.push(make_pair(nr,nc)); s2++; } } } if(s2)bfs(s2,step+1); } int dfs2(int pr,int pc){ canz2++; if(pr==enr&&pc==enc)hzd=1; for(int i=0;i<4;i++){ int nr=pr+dr[i],nc=pc+dc[i]; if(pr>=1&&pr<=n&&pc>=1&&pc<=m&&!vis[nr][nc]&&!vis2[nr][nc]&&t>=maze[nr][nc]){ vis2[nr][nc]=1; dfs2(nr,nc); } } return 0; } int dfs(int pr,int pc,int t){ /* 搜索剩下能走块的最大值canz2,以及标记终点是否在这个块中 */ memset(vis2,0,sizeof vis2); canz2=0,hzd=0; dfs2(pr,pc); if(canz2<t||hzd==0)return 0; if(t==0&&maze[pr][pc]==0)return 1; for(int i=0;i<4;i++){ int nr=pr+dr[i],nc=pc+dc[i]; if(pr>=1&&pr<=n&&pc>=1&&pc<=m&&!vis[nr][nc]&&t>=maze[nr][nc]){ vis[nr][nc]=1; if(dfs(nr,nc,t-1))return 1; vis[nr][nc]=0; } } return 0; } int main(){ while(scanf("%d%d%d",&n,&m,&t),n){ for(int i=1;i<=n;i++){ scanf("%s",smaze[i]+1); for(int j=1;j<=m;j++){ if(smaze[i][j]=='S')str=i,stc=j; if(smaze[i][j]=='D')enr=i,enc=j; } } memset(vis,0,sizeof vis); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)maze[i][j]=10000; maze[enr][enc]=0; vis[enr][enc]=1; q.push(make_pair(enr,enc)); canz=0; /* 找到所有点到终点的最短路,之后DFS时如果剩下的时间T大于这点到终点的最短路, 就不需要继续拓展这个点了,加上这个剪枝基本就能AC了,600MS+.. */ bfs(1,1); /* 1.迷宫中能走的部分<T.必然不能走完 2.起点到终点的最短路>T 3.时间和路径奇偶性不同 */ if(canz<t||maze[str][stc]>t||((t&1)!=(maze[str][stc]&1))){ printf("NO\n"); continue; } memset(vis,0,sizeof vis); vis[str][stc]=1; if(dfs(str,stc,t))printf("YES\n"); else printf("NO\n"); } return 0; }
USACO 4.3.5 Letter Game
这题比较简单,就是给你一个字符串和26个字母对应的分数,现在有一个字典,找出字母频数和小于目标串并且分值最大的所有串,多个串可以拼在一起
虽然没有说明,但是仔细读题可以发现,目标串3~7字符,字典中串也是3~7 也就是说要么选一个,要么选两个...
第一遍找出最大分数,第二遍所有符合条件的串,排序输出即可
/* ID: swm80232 PROG:lgame LANG: C++ */ #include <fstream> #include <cstdio> #include <string> #include <math.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; string col; string dict[40001],dictl; string res[40001]; int score[40001],scn,ds,rp=0; int h[27],h2[27],slen,bscore=0; int sc[26]={2,5,4,4,1,6,5,5,1,7,6,3,5,2,3,5,7,2,1,2,4,6,6,7,5,7}; //读取文件 void read(){ memset(h,0,sizeof h); ds=0; cin>>col; slen=col.length(); for(int i=0;i<slen;i++){ h[col[i]-'a']++; } fstream f("lgame.dict"); while(f>>dictl){ if(dictl==".")break; if(dictl.length()>slen)continue; memset(h2,0,sizeof h2); scn=0; int yes=1; for(int i=0;i<dictl.length();i++){ h2[dictl[i]-'a']++; scn+=sc[dictl[i]-'a']; //如果出现目标串中没有的字母,或者频数大于目标串中字母频数.直接不保存 if(h2[dictl[i]-'a']>h[dictl[i]-'a']){yes=0;break;} } if(!yes)continue; score[ds]=scn; dict[ds++]=dictl; //记录单单词最大分数 bscore=max(bscore,scn); //printf("%d\n",bscore); } f.close(); } //找最大分值 void gbsc(){ for(int i=0;i<ds;i++){ for(int j=i;j<ds;j++){ memset(h2,0,sizeof h2); int yes=1; for(int k=0;k<dict[i].length();k++){ h2[dict[i][k]-'a']++; } for(int k=0;k<dict[j].length();k++){ h2[dict[j][k]-'a']++; //排除不符合条件的 if(h2[dict[j][k]-'a']>h[dict[j][k]-'a'])yes=0; } /* for(int k=0;k<26;k++)printf("%d ",h2[k]); printf("\n"); for(int k=0;k<26;k++)printf("%d ",h[k]); printf("\n"); */ //记录多单词最大分值 if(yes)bscore=max(bscore,score[i]+score[j]); } } } //根据分值找单词 void gbrs(){ for(int i=0;i<ds;i++){ if(score[i]==bscore)res[rp++]=dict[i]; } for(int i=0;i<ds;i++){ for(int j=i;j<ds;j++){ memset(h2,0,sizeof h2); int yes=1; for(int k=0;k<dict[i].length();k++){ h2[dict[i][k]-'a']++; } for(int k=0;k<dict[j].length();k++){ h2[dict[j][k]-'a']++; if(h2[dict[j][k]-'a']>h[dict[j][k]-'a'])yes=0; } if(yes&&bscore==score[i]+score[j]){ res[rp++]=dict[i]+" "+dict[j]; } } } } int main(){ freopen("lgame.in","r",stdin); freopen("lgame.out","w",stdout); read(); gbsc(); printf("%d\n",bscore); gbrs(); sort(res,res+rp); for(int i=0;i<rp;i++){ cout<<res[i]<<endl; } //system("pause"); return 0; }
一道有关联通分量的搜索,第一问枚举每个点搜索起点终点是否可达即可
第二问,很容易证明,符合要求的点必然在第一问的解的集合中,否则起点就可以直通终点了~~至于怎么解决,采用一种染色的方法,将所有起点能到达的点染成绿色,中间点能到达的点染成红色,如果地图上没有一个点被染成两种颜色,则该点是符合条件的点.
/*
ID: swm80232
PROG:race3
LANG: C++
*/
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
using namespace std;
int n,a,map[55][105],a1[105],ap1=0,a2[105],ap2=0,vis[55],v1[55],v2[55];
int dfs(int p){
if(p==n)return 1;
for(int i=1;i<=map[p][0];i++){
if(!vis[map[p][i]]){
vis[map[p][i]]=1;
if(dfs(map[p][i]))return 1;
}
}
return 0;
}
void dfsg(int p,int d){
for(int i=1;i<=map[p][0];i++){
if(map[p][i]!=d&&!v1[map[p][i]]){
v1[map[p][i]]=1;
dfsg(map[p][i],d);
}
}
}
void dfsb(int p){
for(int i=1;i<=map[p][0];i++){
if(!v2[map[p][i]]){
v2[map[p][i]]=1;
dfsb(map[p][i]);
}
}
}
int main(){
freopen("race3.in","r",stdin);
freopen("race3.out","w",stdout);
n=0;
memset(map,0,sizeof map);
while(scanf("%d",&a)){
if(a==-2){n++;continue;}
if(a==-1)break;
map[n][0]++;
map[n][map[n][0]]=a;
}
n--;
for(int i=1;i<n;i++){
memset(vis,0,sizeof vis);
vis[0]=1,vis[i]=1;
if(!dfs(0))a1[ap1++]=i;
}
printf("%d",ap1);
for(int i=0;i<ap1;i++)printf(" %d",a1[i]);
printf("\n");
/*
容易知道,中间点必然是不可避免的点
染色法,从起点出发能到达的点染成红色,从另一个起点出发,能到达
的点染成黑色,如果出现点重复被染色,说明该点不符合要求
*/
for(int i=0;i<ap1;i++){
memset(v1,0,sizeof v1);
memset(v2,0,sizeof v2);
v1[0]=1;
v2[a1[i]]=1;
dfsg(0,a1[i]);
dfsb(a1[i]);
int yes=1;
for(int i=0;i<=n;i++){
if(v1[i]&&v2[i])yes=0;
}
if(yes)a2[ap2++]=a1[i];
}
printf("%d",ap2);
for(int i=0;i<ap2;i++)printf(" %d",a2[i]);
printf("\n");
//system("pause");
return 0;
}
有数学方法,可以找到规律,可是我一眼看上去就直接搜索了..
BFS,自然是超时了..代码写的很丑陋,写了一下午都没A,后来还是看了大牛的代码..这份代码基本都是按照大牛的写法写的,所以想看代码还是直接去NOCOW看吧..
一开始用位保存状态1<<24,然后还要保存空格的位置,开了一个[24][1<<24]的数组,直接爆空间.作罢
其实这题虽然是用搜索来做,但是并不是盲目的搜索,有一种策略,就是尽量让b像左,w向右,所以保证每一步b左移了或者w右移了,相反作用的操作不予考虑..也因为是一种贪心的搜索,所以连判重都免了..不会有重复情况的.
/*
ID: swm80232
PROG:shuttle
LANG: C++
*/
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
using namespace std;
struct node{
int ans[1000];
int p;
int ap;
string s;
}no;
int n;
string des;
void bfs(){
//init
queue<node> q;
//set<string> se;
//se.clear();
while(!q.empty())q.pop();
node n1;
//init start and end
n1.s.resize(2*n+1);
des.resize(2*n+1);
for(int i=0;i<n;i++){n1.s[i]='w';des[i]='b';}
for(int i=n+1;i<2*n+1;i++){n1.s[i]='b',des[i]='w';}
n1.s[n]=des[n]=' ';
n1.p=n,n1.ap=0;
q.push(n1);
while(!q.empty()){
node now=q.front();
node nw;
q.pop();
/*
贪心选择,不需要判重...因为每次只会选择一个方向,不会往回选择
*/
//if(se.count(now.s))continue;
//se.insert(now.s);
//if reach the destination
if(now.s==des){
for(int i=0;i<now.ap;i++){
printf("%d",now.ans[i]+1);
if((i+1)%20==0)printf("\n");
else if(i!=now.ap-1)printf(" ");
}
if(now.ap%20!=0)printf("\n");
return;
}
int p=now.p;
memcpy(nw.ans,now.ans,sizeof now.ans);
//dict order -2 -1 +1 +2
//选择时有一定的贪心性质
if(now.p>1&&now.s[p-1]=='b'&&now.s[p-2]=='w'){
nw.s=now.s;
nw.s[p]=nw.s[p-2],nw.s[p-2]=' ';
nw.p=now.p-2;
nw.ans[now.ap]=nw.p;
nw.ap=now.ap+1;
q.push(nw);
}
if(now.p>0&&now.s[p-1]=='w'){
nw.s=now.s;
nw.s[p]=nw.s[p-1],nw.s[p-1]=' ';
nw.p=now.p-1;
nw.ans[now.ap]=nw.p;
nw.ap=now.ap+1;
q.push(nw);
}
if(now.p<2*n&&now.s[p+1]=='b'){
nw.s=now.s;
nw.s[p]=nw.s[p+1],nw.s[p+1]=' ';
nw.p=now.p+1;
nw.ans[now.ap]=nw.p;
nw.ap=now.ap+1;
q.push(nw);
}
if(now.p<2*n-1&&now.s[p+1]=='w'&&now.s[p+2]=='b'){
nw.s=now.s;
nw.s[p]=nw.s[p+2],nw.s[p+2]=' ';
nw.p=now.p+2;
nw.ans[now.ap]=nw.p;
nw.ap=now.ap+1;
q.push(nw);
}
}
}
int main(){
freopen("shuttle.in","r",stdin);
freopen("shuttle.out","w",stdout);
scanf("%d",&n);
bfs();
//system("pause");
return 0;
}
给N幅画叠放之后的效果,确定叠放的顺序
比较容易想到拓扑排序,先一遍扫描扫描出每个图形的范围(左上角和右下角确定),再一遍扫描建图,如果B压在了A的上面,则建一条B->A的边
因为要输出所有符合要求的排序,所以把拓扑写成DFS的形式就可以了..
/* ID: swm80232 PROG:frameup LANG: C++ */ #include <cstdio> #include <string> #include <math.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; int h,w; struct square{ int has; int ar,ac,br,bc;//右上 左下 square(){has=0,ar=100,ac=100,br=0,bc=0;} }sq[30]; char m[35][35]; int map[31][31]; int in[31],zms=0; int ans[30]; void topo(int pos,int ind[31]){ if(pos==zms+1){ for(int i=1;i<=zms;i++){ printf("%c",ans[i]+'A'-1); } printf("\n"); return; } int p=0; for(p=1;p<=30;p++){ if(ind[p]==0){ ans[pos]=p; int pind[31]; for(int i=1;i<=30;i++)pind[i]=ind[i]; pind[p]=-1; for(int t=1;t<=30;t++){ if(map[p][t])pind[t]--; } topo(pos+1,pind); } } } void fg(int k,int t){ if(k==t||map[k][t])return; map[k][t]=1; in[t]++; } int main(){ freopen("frameup.in","r",stdin); freopen("frameup.out","w",stdout); scanf("%d%d",&h,&w); for(int i=1;i<=h;i++){ scanf("%s",m[i]+1); } for(int i=1;i<=h;i++){ for(int j=1;j<=w;j++){ if(m[i][j]=='.')continue; int t=m[i][j]-'A'+1; sq[t].has=1; sq[t].ar=min(sq[t].ar,i); sq[t].ac=min(sq[t].ac,j); sq[t].br=max(sq[t].br,i); sq[t].bc=max(sq[t].bc,j); } } memset(map,0,sizeof map); memset(in,0,sizeof in); for(int k=1;k<=30;k++){ if(sq[k].has==0){ in[k]=-1; continue; } zms++; for(int i=sq[k].ar;i<=sq[k].br;i++){ int t=m[i][sq[k].ac]-'A'+1; int t2=m[i][sq[k].bc]-'A'+1; fg(k,t); fg(k,t2); } for(int i=sq[k].ac;i<=sq[k].bc;i++){ int t=m[sq[k].ar][i]-'A'+1; int t2=m[sq[k].br][i]-'A'+1; fg(k,t); fg(k,t2); } } topo(1,in); //system("pause"); return 0; }
感觉比较难的一题最小割..而且网上大多的思路和代码都是错的,和YJ学长讨论这一题,YJ学长很快找到了USACO上两份代码的反例,所以只能说这题的数据实在太水了..
第一问找最小割就不用说了,EK就可以了,注意有重边.
第二问找边数最少的最小割,官网给的思路很巧妙,每条边的容量乘以1001+1,再求最大流,求出的最大流为maxflow/1001(假设不加前最大流为maxflow,因为边数小于1000,所以结果不会超过MAXFLOW=maxflow*1001+1000,除去1000就是最大流),这样做的好处是可以直接求出第二问,因为加一条边最大流就加1,所以边数最少的最小割求出的最大流是最小的,这个值就是MAXFLOW%1001
第三问求出最小割的边,这里采用退流的方法,如果去掉这个边之后最大流的减少量等于这条边的容量,则该边为最小割中的一边,并且去掉这条边后,和它组成一组最小割的边仍然构成原图的最小割,注意如果该边不是最小割中的边要把边加回去.至于字典序只要按输入顺序遍历就可以了.
另外这题是大数,注意数据范围
/* ID: swm80232 PROG:milk6 LANG: C++ */ #include <cstdio> #include <string> #include <math.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> #include <queue> using namespace std; typedef long long LL; const LL INF = 1e10; int n,m; LL cap[35][35]; struct side{ LL si,ei,ci; }s[1005]; LL flow[35][35]; LL EK(){ LL a[35],f=0; LL p[35]; memset(flow,0,sizeof flow); memset(p,0,sizeof p); queue<int> q; while(1){ memset(a,0,sizeof a); a[1] = INF; q.push(1); while(!q.empty()){ int u=q.front(); q.pop(); for(LL v=1;v<=n;v++){ if(!a[v]&&cap[u][v]>flow[u][v]){ p[v]=u; q.push(v); a[v]=min(a[u],cap[u][v]-flow[u][v]); } } } if(a[n]==0)break; for(int u=n;u!=1;u=p[u]){ flow[p[u]][u]+=a[n]; flow[u][p[u]]-=a[n]; } f+=a[n]; } return f; } int main(){ freopen("milk6.in","r",stdin); freopen("milk6.out","w",stdout); memset(cap,0,sizeof cap); scanf("%lld%lld",&n,&m); for(LL i=0;i<m;i++)scanf("%lld%lld%lld",&s[i].si,&s[i].ei,&s[i].ci); for(LL i=0;i<m;i++)cap[s[i].si][s[i].ei]+=s[i].ci*1001+1; LL maxflow=EK(); LL mflow=maxflow/1001; LL mside=maxflow%1001; printf("%lld %lld\n",mflow,mside); LL nside=0; for(LL i=0;i<m&&nside<mside;i++){ if(flow[s[i].si][s[i].ei]!=cap[s[i].si][s[i].ei])continue; cap[s[i].si][s[i].ei]-=s[i].ci*1001+1; if(EK()==maxflow-s[i].ci*1001-1){ printf("%lld\n",i+1); maxflow-=s[i].ci*1001+1; nside++; }else cap[s[i].si][s[i].ei]+=s[i].ci*1001+1; } // system("pause"); return 0; }