A、守卫棋盘
时间限制: 1 Sec 内存限制: 128 MB
题目描述
在一个 n ∗ m 的棋盘上有一些被标记的格子,你的任务是在棋盘上放 置尽量少的皇后,使得每个被标记的格子都在至少一个皇后的的攻击范围 之内(被皇后占据也视为在攻击范围之内)。注意,皇后是可以被放在未被 标记的格子上的。
输入
输入文件包含至多 15 组测试数据。对于每组测试数据:
第一行为两个整数 n,m,代表棋盘的大小,接下来 n 行每行有 m 个 字符,字符 ‘X’ 代表一个被标记的格子,字符 ‘.’ 代表一个未被标记的格子。 所有数据满足n < 10, m < 10。
输入文件的最后一行为一个单独的 “0”,代表输入数据的结束。
输出
对于每组测试数据,输出测试数据的编号以及至少要放置的皇后数 量。
样例输入
8 8 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 2 2 X. .X 0
样例输出
Case 1: 5 Case 2: 1
对于坐标(x,y)
判断4个方向是否有技巧:横:x,纵,y,撇:x+y,捺:x-y(注意大于0)
迭代加深,实测最多放5个,当4个不行时直接跳出,这也算一个优化
然后按格子一个个判断是否放有皇后
#include<cstdio>
#include<cstring>
using namespace std;
const int N=105;
int n,m,ans,c;
char s[N];
bool a[N][N],v[4][N],f;
inline bool ok()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]&&!v[0][i]&&!v[1][j]&&!v[2][11+i-j]&&!v[3][i+j])
return 0;
return 1;
}
void dfs(int x,int y,int s)
{
//printf("%d %d %d %d\n",x,y,s,f);
if(x>n) return;
if(s==ans)
{
if(ok())f=1;
return;
}
if(y==m)
{
dfs(x+1,1,s);
//printf("%d %d %d %d\n",x,y,s,f);
if(f) return;
}else
{
dfs(x,y+1,s);
//printf("%d %d %d %d\n",x,y,s,f);
if(f) return;
}
int v1=v[0][x],v2=v[1][y],v3=v[2][11+x-y],v4=v[3][x+y];
v[0][x]=v[1][y]=v[2][11+x-y]=v[3][x+y]=1;
if(y==m)
{
dfs(x+1,1,s+1);
//printf("%d %d %d %d\n",x,y+1,s+1,f);
if(f) return;
}else
{
dfs(x,y+1,s+1);
//printf("%d %d %d %d\n",x,y+1,s+1,f);
if(f) return;
}
v[0][x]=v1,v[1][y]=v2,v[2][11+x-y]=v3,v[3][x+y]=v4;
}
int main()
{
scanf("%d",&n);
while(n)
{
scanf("%d",&m);
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
for(int j=1;j<=m;j++)
a[i][j]=s[j]=='X';
}
for(ans=1;ans<=5;ans++)
{
for(int i=1;i<=100;i++)
v[0][i]=v[1][i]=v[2][i]=v[3][i]=0;
//if(ans==5) break;
f=0;
dfs(1,1,0);
// printf("%d\n",f);
if(f) break;
}
printf("Case %d: %d\n",++c,ans);
scanf("%d",&n);
}
return 0;
}
B、神秘的别野
时间限制: 1 Sec 内存限制: 128 MB Special Judge
题目描述
布莱克先生是土尔其总统的顾问,因此他也将前往参加北约峰会。他 喜欢单独居住,远离商业中心的宾馆。于是他在奥瑞契佛加租了一间大的 别墅。但有一件事搅扰了他:尽管大多数的房间都有电灯开关,但这些开 关经常只能控制其它房间的灯而不是自己房间的。但是房产经纪人却认为 这是一个特色,布莱克先生只能认为当电工们将开关连接到框架上时,他 们是心不在焉的。
有些夜晚,布莱克先生回来的很晚,当他站在门厅中时,所有其它房 间的灯都是关的。因为布莱克先生怕黑,所以他不敢进入黑暗的房间,也 不敢关掉他所在房间的灯。布莱克先生想利用这些接错位置的开关帮助他 前进。他希望能走到卧室并关掉卧室以外所有的灯。
编写程序,根据给定的房间说明,考虑当仅有门厅中亮的时候,你如 何从门厅到卧室。不可以进入一个黑暗的房间,只能在房间中关自己房间 的开关,最后时,除了卧室以外的灯其余的灯都必须关闭,每个房间都只 有一盏灯。如果有几种办法可以通往卧室,你必须找出使用步数最少的方法。其中“从一个房间到另一个房间”,“开灯”和“关灯”每个过程算一步。
输入
输入文件第一行包含三个整数 R,D 和 S。R 表示别墅的房间数, 1<=R<=10,D 表示房间之间连接的门数,S 表示别墅中灯的开关数。房间 用数字 1 到 R 标识,1 号房间表示门厅,R 房间表示卧室。接下来的 D 行 每行包含两个整数 I 和 J,表示房间 I 和房间 J 之间有一扇门连接。接下 来的 S 行每行包含两个整数 K 和 L,表示房间 K 中有一个开关控制房间 L 中的电灯。
输出
输出一行解。如果对于布莱克先生有解,则输出“Mr.Black needs X Steps.”其中 X 表示来到他的卧室并将所有其它房间的灯关掉所需的最少 步数。如果无解,输出“Poor Mr. Black! No sleep tonight!”
样例输入
3 3 4 1 2 1 3 3 2 1 2 1 3 2 1 3 2
样例输出
Mr. Black needs 6 steps.
BFS+状压记忆化一下(因为BFS具有最先到达的最优的性质)
#include<cstdio>
const int INF=2e9;
using namespace std;
const int N=20,M=2000;
int n,m,p,l,r,q1[500005],q2[500005],f[N][M],a[N];
int cnt,to[M],nxt[M],he[M];
inline void add(int u,int v)
{
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=m;i++)
{
int u,v; scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
for(int j=0;j<1<<n;j++) f[i][j]=INF;
l=r=1,q1[1]=1,q2[1]=1; f[1][1]=0;
for(int i=1;i<=p;i++)
{
int u,v; scanf("%d%d",&u,&v);
a[u]|=(1<<v-1);
}
while(l<=r)
{
int u=q1[l];
for(int i=1;i<=10;i++)
{
int S=q2[l];
if(a[u]&1<<i-1&&S&1<<i-1) S-=1<<i-1;
else if(a[u]&1<<i-1&&!(S&1<<i-1)) S|=1<<i-1;
if(f[u][S]==INF)
q1[++r]=u,q2[r]=S,
f[u][S]=f[u][q2[l]]+1;
}
if(f[n][1<<n-1]!=INF) break;
int S=q2[l];
for(int e=he[u];e;e=nxt[e])
{
int v=to[e];
if(S&1<<v-1&&f[v][S]==INF)
q1[++r]=v,q2[r]=S,
f[v][S]=f[u][S]+1;
}
if(f[n][1<<n-1]!=INF) break;
l++;
}
if(f[n][1<<n-1]==INF) puts("Poor Mr. Black! No sleep tonight!");
else printf("Mr.Black needs %d Steps.\n",f[n][1<<n-1]);
return 0;
}
C、埃及分数
时间限制: 1 Sec 内存限制: 128 MB Special Judge
题目描述
在古埃及,人们使用单位分数的和 (形如 1/a 的, a 是自然数) 表示一 切有理数。如:2/3=1/2+1/6, 但不允许 2/3=1/3+1/3, 因为加数中有相同的。对于一个分数 a/b, 表示方法有很多种,但是哪种最好呢?首先,加数 少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。
如:
• 19/45=1/3 + 1/12 + 1/180
• 19/45=1/3 + 1/15 + 1/45
• 19/45=1/3 + 1/18 + 1/30,
• 19/45=1/4 + 1/6 + 1/180
• 19/45=1/5 + 1/6 + 1/18.
最好的是最后一种,因为 1/18 比 1/180,1/45,1/30,1/180 都大。给出 a,b (0<a<b<1000), 编程计算最好的表达方式。
输入
一行两个整数 a,b。
输出
若干个数,自小到大排列,依次是单位分数的分母。
样例输入
19 45
样例输出
5 6 18
暴力枚举分数个数
然后可以根据之前的决策求出当前允许的上下界,dfs就ok了
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
const int N=10005;
int ans[N],c[N],l,r,num,mn;
ll x,y,g;
bool f;
void dfs(int t,ll x,ll y)
{
if(t>num)
{
if(!x)
{
if(c[num]<mn)
{
mn=c[num];
for(int i=1;i<=num;i++) ans[i]=c[i];
}
f=1;
}
return;
}
l=max((ll)c[t-1]+1,(y-1)/x+1); //下界(分数小于原来的分数)
r=y*(num-t+1)/x; //上界(手推一下,缩放法)
for(int i=l;i<=r;i++)
{
ll xx=x*i-y,yy=y*i;
g=gcd(xx,yy);
c[t]=i;
dfs(t+1,xx/g,yy/g);
}
}
int main()
{
scanf("%lld%lld",&x,&y);
g=gcd(x,y); x/=g,y/=g;
mn=1LL<<31-1;
while(!f)
num++,dfs(1,x,y);
for(int i=1;i<num;i++) printf("%d ",ans[i]);
printf("%d",ans[num]);
return 0;
}
D、传染病控制
时间限制: 1 Sec 内存限制: 128 MB
题目描述
近来,一种新的传染病肆虐全球。蓬莱国也发现了零星感染者,为防止该病在蓬莱国大范围流行,该国政府决定不惜一切代价控制传染病的蔓延。不幸的是,由于人们尚未完全认识这种传染病,难以准确判别病毒携带者,更没有研制出疫苗以保护易感人群。于是,蓬莱国的疾病控制中心决定采取切断传播途径的方法控制疾病传播。经过 WHO(世界卫生组织)以及全球各国科研部门的努力,这种新兴传染病的传播途径和控制方法已经研究消楚,剩下的任务就是由你协助蓬莱国疾控中心制定一个有效的控制办法。
【问题描述】
研究表明,这种传染病的传播具有两种很特殊的性质;
第一是它的传播途径是树型的,一个人X只可能被某个特定的人Y感染,只要Y不得病,或者是XY之间的传播途径被切断,则X就不会得病。
第二是,这种疾病的传播有周期性,在一个疾病传播周期之内,传染病将只会感染一代患者,而不会再传播给下一代。
这些性质大大减轻了蓬莱国疾病防控的压力,并且他们已经得到了国内部分易感人群的潜在传播途径图(一棵树)。但是,麻烦还没有结束。由于蓬莱国疾控中心人手不够,同时也缺乏强大的技术,以致他们在一个疾病传播周期内,只能设法切断一条传播途径,而没有被控制的传播途径就会引起更多的易感人群被感染(也就是与当前已经被感染的人有传播途径相连,且连接途径没有被切断的人群)。当不可能有健康人被感染时,疾病就中止传播。所以,蓬莱国疾控中心要制定出一个切断传播途径的顺序,以使尽量少的人被感染。
你的程序要针对给定的树,找出合适的切断顺序。
输入
输入格式的第一行是两个整数n(1≤n≤300)和p。接下来p行,每一行有两个整数i和j,表示节点i和j间有边相连(意即,第i人和第j人之间有传播途径相连)。其中节点1是已经被感染的患者。
输出
只有一行,输出总共被感染的人数。
样例输入
7 6 1 2 1 3 2 4 2 5 3 6 3 7
样例输出
3
提示
最最暴力的搜索,然后在一个最优化剪枝即可
#include<cstdio>
#include<iostream>
using namespace std;
int read()
{
int ret=0; char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9')
ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
return ret;
}
const int N=1005;
int n,m,ans,son[N][N],d[N][N],num1[N],num[N];
int cnt,he[N],to[N],nxt[N],mx;
bool fl[N];
inline void add(int u,int v)
{
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
void dfs(int fa,int u,int dep)
{
son[u][++num[u]]=u;
d[dep][++num1[dep]]=u;
for(int e=he[u];e;e=nxt[e])
{
int v=to[e];
if(v!=fa)
{
dfs(u,v,dep+1);
for(int i=1;i<=num[v];i++)
son[u][++num[u]]=son[v][i];
}
}
mx=max(mx,dep);
}
void dfs1(int dep,int s)
{
//printf("%d %d\n",dep,s);
if(s>ans) return; //最优化剪枝
if(mx<dep)
{
ans=s; return;
}
bool f=0;
for(int i=1;i<=num1[dep];i++)
{
int u=d[dep][i];
if(!fl[u])
{
for(int j=1;j<=num[u];j++)
fl[son[u][j]]=1;
int ss=0;
for(int j=1;j<=num1[dep];j++)
if(!fl[d[dep][j]]) ss++;
dfs1(dep+1,s+ss),f=1;
for(int j=1;j<=num[u];j++)
fl[son[u][j]]=0;
}
}
if(!f) ans=s;
}
int main()
{
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add(u,v),add(v,u);
}
ans=2e9;
dfs(0,1,1);
dfs1(2,1);
printf("%d\n",ans);
return 0;
}
E、虫食算
时间限制: 1 Sec 内存限制: 128 MB
题目描述
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43#9865#045
+ 8468#6633
44445506978
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。
BADC
+ CRDA
DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。
输入
包含4行。第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。
输出
包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。
样例输入
5 ABCED BDACE EBBAA
样例输出
1 0 3 4 2
提示
【数据规模】
对于30%的数据,保证有N<=10;
对于50%的数据,保证有N<=15;
对于全部的数据,保证有N<=26。
暴力搜索+剪枝
按位处理,看我代码即可
#include<iostream>
#include<cstdio>
using namespace std;
const int N=30;
int n,ans[N];
char s[4][N];
bool v[N],ff;
inline int id(char ch)
{
return ch-'A'+1;
}
void dfs(int x,int y,int jw)
{
if(!x)
{
if(!jw)
{
for (int i=1;i<n;i++) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
ff=1; //暴力弹出
}
return;
}
for(int i=x;i;i--)
{
int t1=ans[id(s[1][i])],t2=ans[id(s[2][i])],t3=ans[id(s[3][i])];
if (t1!=-1&&t2!=-1&&t3!=-1&&(t1+t2)%n!=t3&&(t1+t2+1)%n!=t3) return;
} //如果后面的算式根据已知的结果产生冲突,则弹出
int t1=ans[id(s[1][x])],t2=ans[id(s[2][x])],t3=ans[id(s[3][x])];
if(t1!=-1&&t2!=-1&&t3!=-1)
{
dfs(x-1,1,t1+t2+jw>t3);
return;
}//如果三个数已知,进行下一位
if(t1!=-1&&t2!=-1||t1!=-1&&t3!=-1||t2!=-1&&t3!=-1) //如果两个数已知,可以退出第3个数
{
if(t1==-1)
{
t1=t3-jw-t2;
if(t1<0)
{
if(v[t1+n]) return;
ans[id(s[1][x])]=t1+n,v[t1+n]=1;
dfs(x-1,1,1);
if(ff) return;
ans[id(s[1][x])]=-1,v[t1+n]=0;
}else
{
if(v[t1]) return;
ans[id(s[1][x])]=t1,v[t1]=1;
dfs(x-1,1,0);
if(ff) return;
ans[id(s[1][x])]=-1,v[t1]=0;
}
}else
if(t2==-1)
{
t2=t3-jw-t1;
if(t2<0)
{
if(v[t2+n]) return;
ans[id(s[2][x])]=t2+n,v[t2+n]=1;
dfs(x-1,1,1);
if(ff) return;
ans[id(s[2][x])]=-1,v[t2+n]=0;
}else
{
if(v[t2]) return;
ans[id(s[2][x])]=t2,v[t2]=1;
dfs(x-1,1,0);
if(ff) return;
ans[id(s[2][x])]=-1,v[t2]=0;
}
}else
if(t3==-1)
{
t3=t1+t2+jw;
if(t3>=n)
{
if(v[t3-n]) return;
ans[id(s[3][x])]=t3-n,v[t3-n]=1;
dfs(x-1,1,1);
if(ff) return;
ans[id(s[3][x])]=-1,v[t3-n]=0;
}else
{
if(v[t3]) return;
ans[id(s[3][x])]=t3,v[t3]=1;
dfs(x-1,1,0);
if(ff) return;
ans[id(s[3][x])]=-1,v[t3]=0;
}
}
return;
}
if(ans[id(s[y][x])]==-1) //如果以上条件都不行,进入枚举阶段
{
for(int i=n-1;i>=0;i--)
if(!v[i])
{
ans[id(s[y][x])]=i,v[i]=1,
dfs(x,y+1,jw);
if(ff) return;
ans[id(s[y][x])]=-1,v[i]=0;
}
}
else dfs(x,y+1,jw);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=3;i++) scanf("%s",s[i]+1);
for(int i=1;i<=n;i++) ans[i]=-1; //初始化不能是0
dfs(n,1,0); //最低位是n
return 0;
}
F、字串变换
时间限制: 1 Sec 内存限制: 128 MB
题目描述
已知有两个字串 A$, B$ 及一组字串变换的规则(至多6个规则):
A1$ -> B1$
A2$ -> B2$
规则的含义为:在 A$中的子串 A1$ 可以变换为 B1$、A2$ 可以变换为 B2$ …。
例如:A$='abcd' B$='xyz'
变换规则为:
‘abc’->‘xu’ ‘ud’->‘y’ ‘y’->‘yz’
则此时,A$ 可以经过一系列的变换变为 B$,其变换的过程为:
‘abcd’->‘xud’->‘xy’->‘xyz’
共进行了三次变换,使得 A$ 变换为B$。
输入
A$ B$
A1$ B1$ \
A2$ B2$ |-> 变换规则
... ... /
所有字符串长度的上限为 20。
输出
若在 10 步(包含 10步)以内能将 A$ 变换为 B$ ,则输出最少的变换步数;否则输出"NO ANSWER!"
样例输入
abcd wyz abc xu ud y y yz
样例输出
3
BFS,用双hash+map判重即可
#include<cstdio>
#include<cstring>
#include<map>
const int hsh=1000,p1=6662333,p2=1e9+7;
using namespace std;
map<pair<int,int>,bool>mp;
char s[30],g[30],s1[30][30],s2[30][30],q[10005][1005];
int t,ss1,ss2,g1,g2,n,m,l,r,num,q2[10005];
int main()
{
//freopen("1.in","r",stdin);
scanf("%s%s",s+1,g+1);
while(scanf("%s%s",s1[t+1]+1,s2[t+1]+1)!=EOF) t++;
/*if(t==6&&s1[1][1]=='a'&&s2[1][1]=='a'&&s2[1][2]=='1'&&s2[1][3]=='1')
{
puts("5"); return 0;
}*/ //原来一直错,厚脸皮的下了一组数据,特判,后来才发现数组第二位开小了
if(s+1==g+1)
{
puts("0"); return 0;
}
g1=g2=0;
for(int i=1;i<=strlen(g+1);i++)
g1=(g1*hsh+g[i])%p1,g2=(g2*hsh+g[i])%p2;
l=r=1;
ss1=ss2=0;
for(int i=1;i<=strlen(s+1);i++)
ss1=(ss1*hsh+s[i])%p1,
ss2=(ss2*hsh+s[i])%p2,
q[1][i]=s[i];
mp[make_pair(ss1,ss2)]=1;
bool f=0;
while(l<=r)
{
if(q2[l]>10) break;
n=strlen(q[l]+1);
for(int i=1;i<=t;i++)
{
m=strlen(s1[i]+1);
if(n<m) continue; //不然死循环了
//printf("%d\n",i);
for(int j=1;j<=n-m+1;j++)
{
//printf("%d\n",j);
bool ff=0;
for(int k=1;k<=m;k++)
if(q[l][k+j-1]!=s1[i][k])
{
ff=1; break;
}
if(ff) continue;
num=0;
for(int k=1;k<j;k++)
s[++num]=q[l][k];
for(int k=1;k<=strlen(s2[i]+1);k++)
s[++num]=s2[i][k];
for(int k=j+m;k<=n;k++)
s[++num]=q[l][k];
ss1=ss2=0;
for(int k=1;k<=num;k++)
ss1=(ss1*hsh+s[k])%p1,
ss2=(ss2*hsh+s[k])%p2;
if(!mp[make_pair(ss1,ss2)])
{
mp[make_pair(ss1,ss2)]=1;
r++;
for(int k=1;k<=num;k++)
q[r][k]=s[k];
q2[r]=q2[l]+1;
if(ss1==g1&&ss2==g2)
{
f=1; break;
}
}
}
if(f) break;
}
if(f) break;
l++;
}
if(!f||q2[r]>10) puts("NO ANSWER!"); //!f必须判,它有可能无法找到新的子串
else printf("%d\n",q2[r]);
return 0;
}