[POJ 4001-4010][2011 Asia Fuzhou Regional Contest]2011 ACM 福州赛区现场赛题解(不断更新)

A:Xiangqi(http://poj.org/problem?id=4001)

恶心的模拟题,写的时候一定要小心,枚举棋盘上所有的点判断能否将掉黑帅下一步到达的位置即可,思路很简单但是AC也很难

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>

#define MAXN 100

using namespace std;

int map[MAXN][MAXN],N; //map[x][y]=kind表示坐标(x,y), 第kind种棋,1->帅 2->车 3->马 4->炮 5->黑帅,竖着x,横着y
int xx[]={1,-1,0,0},yy[]={0,0,1,-1}; //车帅卒的行走方向
int mx[]={-2, -2, -1, 1, 2, 2, 1, -1},my[]= {1, -1, 2, 2, 1, -1, -2, -2} ;//马的行走方向

bool inScope(int x,int y) //判断(x,y)是否在帅的活动范围内
{
    if(y<4||y>6||x<1||x>3) return false;
    return true;
}

bool inMap(int x,int y) //判断(x,y)是否在棋盘内
{
    if(x<1||x>10||y<1||y>9) return false;
    return true;
}

int getNum(int x1,int y1,int x2,int y2) //求(x1,y1)到(x2,y2)之间棋子的个数
{
    if((x1==x2&&y1==y2)||(x1!=x2&&y1!=y2)) return -1;
    if(x1==x2)
    {
        int cnt=0;
        if(y1>y2) swap(y1,y2);
        for(int i=y1+1;i<y2;i++)
            if(map[x1][i]) cnt++;
        return cnt;
    }
    if(y1==y2)
    {
        int cnt=0;
        if(x1>x2) swap(x1,x2);
        for(int i=x1+1;i<x2;i++)
            if(map[i][y1]) cnt++;
        return cnt;
    }
}

bool check(int x,int y) //黑帅在(x,y),可以被打死返回true,不能被打死返回false
{
    for(int i=1;i<=10;i++)
        for(int j=1;j<=9;j++)
        {
            switch(map[i][j])
            {
                case 5:break;
                case 1: //帅
                    {
                        if(getNum(x,y,i,j)==0)
                        {
                            //cout<<x<<' '<<y<<' '<<endl<<i<<' '<<j<<' '<<map[i][j]<<endl;
                            return true;
                        }
                        break;
                    }
                case 2: //车
                    {
                        if(getNum(x,y,i,j)==0)
                        {
                            //cout<<x<<' '<<y<<' '<<endl<<i<<' '<<j<<' '<<map[i][j]<<endl;
                            return true;
                        }
                        break;
                    }
                case 4: //炮
                    {
                        if(getNum(x,y,i,j)==1)
                        {
                            //cout<<x<<' '<<y<<' '<<endl<<i<<' '<<j<<' '<<map[i][j]<<endl;
                            return true;
                        }
                        break;
                    }
                case 3: //马
                    {
                        for(int dir=0;dir<8;dir++)
                        {
                            int tx=i+mx[dir],ty=j+my[dir]; //移动后的马坐标(tx,ty)
                            if(!inMap(tx,ty)) continue;
                            if(map[tx][ty]!=5) continue; //根本打不到黑帅
                            if(mx[dir]==2) if(inMap(i+1,j)&&map[i+1][j]) break; //有棋子挡道
                            if(mx[dir]==-2) if(inMap(i-1,j)&&map[i-1][j]) break;
                            if(my[dir]==2) if(inMap(i,j+1)&&map[i][j+1]) break;
                            if(my[dir]==-2) if(inMap(i,j-1)&&map[i][j-1]) break;
                            //cout<<x<<' '<<y<<' '<<endl<<i<<' '<<j<<' '<<map[i][j]<<endl;
                            return true;
                        }
                        break;
                    }
            }
        }
    return false;
}

int main()
{
    while(1)
    {
        memset(map,0,sizeof(map));
        int hx,hy,x,y,flag=1,now=0; //黑帅坐标(hx,hy),若黑帅没被将死,flag=0
        char s[3];
        scanf("%d%d%d",&N,&hx,&hy);
        if(N==0&&hx==0&&hy==0) return 0;
        map[hx][hy]=5;
        for(int i=0;i<N;i++)
        {
            scanf("%s%d%d",s,&x,&y);
            switch(s[0])
            {
                case 'G':{map[x][y]=1; break;}
                case 'R':{map[x][y]=2; break;}
                case 'H':{map[x][y]=3; break;}
                case 'C':{map[x][y]=4; break;}
            }
        }
        for(int dir=0;dir<4;dir++)
        {
            int bak; //备份
            int tx=hx+xx[dir],ty=hy+yy[dir]; //移动后的黑帅坐标(tx,ty)
            if(!inScope(tx,ty)) continue;
            bak=map[tx][ty];
            map[tx][ty]=5;
            map[hx][hy]=0;
            if(!check(tx,ty)) {flag=0; break;} //黑帅活下来了
            map[tx][ty]=bak;
            map[hx][hy]=5;
        }
        if(flag) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}


B:Alice's Mooncake Shop(http://poj.org/problem?id=4002)

单调队列,此题还没搞清楚,就不写思路了

#include <iostream>
#include <stdio.h>
#include <string.h>

#define MAXT 3000

using namespace std;

struct Node
{
    int num,time; //月饼:时刻为t时费用为num;顾客:时刻为t时要num个月饼
}q[MAXT*4],order[MAXT];

int MonDay[]={0,31,28,31,30,31,30,31,31,30,31,30,31}; //每个月的天数

int mon(char *s)
{
    if(strcmp(s,"Jan")==0) return 1;
    if(strcmp(s,"Feb")==0) return 2;
    if(strcmp(s,"Mar")==0) return 3;
    if(strcmp(s,"Apr")==0) return 4;
    if(strcmp(s,"May")==0) return 5;
    if(strcmp(s,"Jun")==0) return 6;
    if(strcmp(s,"Jul")==0) return 7;
    if(strcmp(s,"Aug")==0) return 8;
    if(strcmp(s,"Sep")==0) return 9;
    if(strcmp(s,"Oct")==0) return 10;
    if(strcmp(s,"Nov")==0) return 11;
    if(strcmp(s,"Dec")==0) return 12;
}

bool isLeapYear(int year) //判定闰年
{
    return year%4==0&&(year%100||year%400==0);
}

int getHour(int year,int month,int day,int hour) //日期化为小时
{
    int tot=0;
    for(int i=2000;i<year;i++)
    {
        if(isLeapYear(i)) tot+=366; //闰年366天
        else tot+=365; //平常365天
    }
    for(int i=1;i<month;i++)
    {
        if(isLeapYear(year)&&i==2) tot+=29; //闰年2月29天
        else tot+=MonDay[i];
    }
    tot+=day-1; //加上天数
    return tot=tot*24+hour;
}

int main()
{
    while(1)
    {
        char s[10];
        int n,m,year,month,day,hour,T,S,cost,h=0,t=0,p=0;
        long long int sum=0;
        scanf("%d%d",&n,&m);
        if(n==0&&m==0) return 0;
        for(int i=0;i<n;i++)
        {
            scanf("%s%d%d%d%d",s,&day,&year,&hour,&order[i].num);
            order[i].time=getHour(year,mon(s),day,hour); //存入顾客需求,将日期化为小时
        }
        scanf("%d%d",&T,&S);
        for(int i=0;i<m;i++)
        {
            scanf("%d",&cost);
            while(h<t&&q[t-1].num+S*(i-q[t-1].time)>=cost) t--; //对于第x个订单,其交付日期为j,制作日期为i,保存花费为s,则其总代价为cost[x]+(j-i)*s,维护队尾单调性
            q[t].num=cost,q[t++].time=i; //新状态入队
            while(p<n&&i==order[p].time)  //处理第p个订单
            {
                while(q[h].time+T<i) h++; //加上保质期后时间还不能到达订单时间(队首的时间太早了),队首出队
                sum+=(q[h].num+S*(i-q[h].time))*order[p++].num; //采用时间最早的状态
            }
        }
        printf("%lld\n",sum);
    }
    return 0;
}


F:Genghis Khan the Conqueror (http://poj.org/problem?id=4006)

最小生成树,不能对于Q次询问跑Q次最小生成树,一定会TLE,需要跑1次最小生成树+1次DFS,求出最小生成树上每两个相邻节点的非树边中的最短边,然后对于Q次询问,每次如果更新的边在树边上时,选择取非树边的最短边或更新后的边去代替生成树上的原有边,即可用O(Q)的复杂度解决

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>

#define INF 1000000000
#define MAXM 3010 //点
#define MAXN 9000100 //边

using namespace std;

vector<int>mst[MAXM];

struct edge
{
    int u,v,c;
    bool operator<(const edge&rhs)const
    {
        return c<rhs.c;
    }
}edges[MAXN];

int nCount=0; //total edges
int num[MAXM][MAXM],best[MAXM][MAXM];
int f[MAXM];
int inTree[MAXM][MAXM];

int min(int a,int b)
{
	if(a<b) return a;
	return b;
}

int findSet(int x)
{
	if(f[x]==x) return x;
	return f[x]=findSet(f[x]);
}

void Union(int a,int b)
{
	f[a]=b;
}

double Kruscal(int n,int m)
{
	int i,j;
	double ans=0;
	sort(edges,edges+m);
	for(i=0;i<m;i++)
	{
		int t1=findSet(edges[i].u);
		int t2=findSet(edges[i].v);
		if(t1!=t2)
		{
			Union(t1,t2);
			ans+=edges[i].c;
			mst[edges[i].u].push_back(edges[i].v);
			mst[edges[i].v].push_back(edges[i].u);
			inTree[edges[i].u][edges[i].v]=1;
			inTree[edges[i].v][edges[i].u]=1;
		}
	}
	return ans;
}

int dfs(int rt,int u,int fa)
{
	int i,j,s=INF;
	for(i=0;i<mst[u].size();i++) //对u结点下的儿子i遍历
	{
		int v=mst[u][i]; //当前结点u儿子为v
		if(v==fa) continue;
		int tmp=dfs(rt,v,u);
		s=min(s,tmp);
		best[u][v]=best[v][u]=min(best[u][v],tmp);
	}
	if(rt!=fa) //不是
		s=min(num[rt][u],s);
	return s;
}

int main()
{
	int i,j;
	int N,M,X,Y,C,Q;
	while(1)
	{
		double ANS=0;
		for(i=0;i<MAXM;i++)
        {
            mst[i].clear();
            f[i]=i;
            for(j=0;j<MAXM;j++)
            {
                num[i][j]=best[i][j]=INF;
                inTree[i][j]=0;
            }
		}
		scanf("%d%d",&N,&M);
		if(N==0&&M==0)
			return 0;
		for(i=0;i<M;i++)
		{
			scanf("%d%d%d",&edges[i].u,&edges[i].v,&edges[i].c);
            num[edges[i].u][edges[i].v]=num[edges[i].v][edges[i].u]=edges[i].c;
		}
		double minDis=Kruscal(N,M);
		for(i=0;i<N;i++)
			dfs(i,i,-1);
		scanf("%d",&Q);
		for(i=1;i<=Q;i++)
		{
			scanf("%d%d%d",&X,&Y,&C);
			if(!inTree[X][Y]) ANS+=minDis; //该边不在树上,最小生成树不变
			else //否则,最小生成树上的边X<->Y选择改成另一最短的边或边权为C的新边
			{
				ANS+=minDis-num[X][Y]+min(best[X][Y],C);
			}
		}
		printf("%.4lf\n",ANS/(Q*1.0));
	}
	return 0;
}


G:Flood-it!(http://poj.org/problem?id=4007)

暴搜。比赛的时候以为要用康托展开和逆康拓展开来判重就没做,其实裸IDA*就能过,一定要注意在IDA*搜索前先进行一次flood操作,否则会死循环,郁闷

//IDA*
#include <iostream>
#include <stdio.h>
#include <string.h>

#define MAXN 10

using namespace std;

int map[MAXN][MAXN],xx[4]={1,-1,0,0},yy[4]={0,0,1,-1},status[MAXN][MAXN]; //status[i][j]=1表示(i,j)已被染色,在联通块内,2表示(i,j)没被染色,与联通块相邻,3表示(i,j)未被染色,与联通块不相邻
int N,depth;

bool check(int x,int y) //检查(x,y)是否越界,是返回false
{
    if(x>N||x<1||y>N||y<1) return false;
    return true;
}

void flood(int x,int y,int col) //以(x,y),颜色为col的格子为起点flood
{
    status[x][y]=1; //将当前格子加入联通块
    for(int dir=0;dir<4;dir++)
    {
        int tx=x+xx[dir]; //移动格子到相邻的(tx,ty)
        int ty=y+yy[dir];
        if(!check(tx,ty)) continue; //越界则跳过
        if(status[tx][ty]==1) continue; //在联通块内,跳过
        if(map[tx][ty]==col) //是可以染的颜色
            flood(tx,ty,col);
        else status[tx][ty]=2; //符合条件但是不能染色,则表明(tx,ty)是与联通块相邻的格子
    }
}

int get_cnt(int col) //如果对剩余的col色方块染色,最多能染多少个方块
{
    int cnt=0;
    for(int i=1;i<=N;i++)
        for(int j=1;j<=N;j++)
        {
            if(status[i][j]==2&&map[i][j]==col) //如果当前格子颜色与col相同且与联通块相邻,对它和相邻的方块染色,由于染色后会更新status数组,所以不会存在重复问题
            {
                cnt++; //增加
                flood(i,j,col);
            }
        }
    return cnt;
}

int h() //估价函数,表示除了联通块的部分,还有多少种颜色,颜色数代表对当前状态未来步数的估计
{
    int tot=0;
    bool flag[6];
    memset(flag,false,sizeof(flag));
    for(int i=1;i<=N;i++)
        for(int j=1;j<=N;j++)
        {
            if(status[i][j]!=1&&!flag[map[i][j]]) //当前点不在联通块内,且该颜色是新种类颜色
            {
                tot++;
                flag[map[i][j]]=true;
            }
        }
    return tot;
}

bool IDAstar(int dep) //在深度为dep的状态下搜索,搜索成功返回true,不成功返回false
{
    if(dep==depth) return h()==0; //搜索深度到达目标深度,返回状态是否与目标状态相同
    if(dep+h()>depth) return false; //预计深度超了,直接返回false
    for(int i=0;i<6;i++) //染第i种颜色
    {
        int now[MAXN][MAXN]; //now数组用于备份status
        memcpy(now,status,sizeof(status));
        if(get_cnt(i)==0) //染不了任何一个方块,即无法染色
            continue;
        if(IDAstar(dep+1)) return true; //加深搜索可行,返回true
        memcpy(status,now,sizeof(now)); //将status数组复原
    }
    return false; //六种颜色都不行,则搜索失败
}

int main()
{
    while(1)
    {
        memset(map,0,sizeof(map));
        memset(status,0,sizeof(status));
        scanf("%d",&N);
        if(N==0) return 0;
        for(int i=1;i<=N;i++)
            for(int j=1;j<=N;j++)
                scanf("%d",&map[i][j]);
        flood(1,1,map[1][1]); //先操作一次
        depth=h();
        while(1)
        {
            if(IDAstar(0)) break; //搜索成功则跳出
            depth++; //不成功则增大深度阈值,重新搜索(迭代加深)
        }
        printf("%d\n",depth); //输出深度
    }
    return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值