解题报告
前言
赛后AK
VIJOS 1389 婚礼上的小杉
题目
总计N封的信件,每个信件都有自己的特征码和序号。
请按照序号递增的顺序输出信件的特征码
每行一个特征码,且特征码的格式应与输入完全一致
代码(过水)
#include <cstdio>
#include <algorithm>
#define rr register
using namespace std;
struct rec{int rk; char s[301];}a[1001]; int n;
signed cmp(rec x,rec y){return x.rk<y.rk;}
signed main(){
rr char c=getchar();
while (c!='\n'){
rr int s=0;
while (c<48||c>57) c=getchar();
while (c>47&&c<58) s=(s<<3)+(s<<1)+c-48,c=getchar();
a[++n].rk=s;
}
for (rr int i=1;i<=n;++i) scanf("%s",a[i].s+1);//完全相同就用字符串
sort(a+1,a+1+n,cmp);//虽然时间多了,但是没关系
for (rr int i=1;i<=n;++i) printf("%s\n",a[i].s+1);
return 0;
}
VIJOS 1390 玩诈欺的小杉
题目
在小杉的面前有一个 N N N行 M M M列的棋盘,棋盘上有 N × M N\times M N×M个有黑白棋的棋子(一面为黑,一面为白),一开始都是白面朝上。可以对任意一个格子进行至多一次的操作(最多进行 N × M N\times M N×M个操作),该操作使得与该格同列的上下各2个格子以及与该格同行的左右各1个格子以及该格子本身翻面。问有多少种方法使初始状态到目标状态(不同当且仅当操作集合不同)
分析
然而可以枚举第一列,通过第一列来判断是否能回到初始状态
代码
#include <iostream>
#define rr register
using namespace std;
int n,m,t,tota,f[21];
signed main(){
cin.tie(0); cout.tie(0); ios::sync_with_stdio(0);
cin>>n>>m>>t;
while (t--){
for (rr int i=1;i<=m;++i) f[i]=0;
for (rr int i=1;i<=n;++i){
rr char c;
for (rr int j=1;j<=m;++j)
cin>>c,f[j]|=(c-48)<<i-1;
}
rr int ans=0;
for (rr int i=0;i<1<<n;++i){
rr int now=i,old=0;
for (rr int j=1;j<=m;++j){
rr int temp=now;
now=((now>>1)^(now>>2)^(now<<1)^(now<<2)^now^old^f[j])&((1<<n)-1);//上面1行,上面第2行,下面1行,下面第2行,合并前1列和该列以及目标状态列的异或值
old=temp;
}
if (!now) ++ans;//恢复0
}
printf("%d\n",ans);
}
return 0;
}
VIJOS 1391 想越狱的小杉
分析
然而这道题就是单源最短路径的最短边最长问题,很容易得到松弛操作 d i s [ t o ] = m i n ( d i s [ n o w ] , w ) dis[to]=min(dis[now],w) dis[to]=min(dis[now],w)
代码
#include <cstdio>
#include <queue>
#define rr register
using namespace std;
struct node{int y,w,next;}e[400001];
int n,k,ls[2001],dis[2001],v[2001];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
return ans;
}
void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
signed main(){
n=iut();
while (1){
rr int x=iut(),y=iut(),w=iut();
if (!(x&&y&&w)) break;
e[++k]=(node){y,w,ls[x]}; ls[x]=k;
}
v[1]=1; rr queue<int>q; q.push(1); dis[1]=2147483647;
while (q.size()){
rr int x=q.front(); q.pop();
for (rr int i=ls[x];i;i=e[i].next)
if (dis[e[i].y]<min(dis[x],e[i].w)){
dis[e[i].y]=min(dis[x],e[i].w);
if (!v[e[i].y]) v[e[i].y]=1,q.push(e[i].y);
}
v[x]=0;
}
for (rr int i=2;i<=n;++i){
if (!dis[i]) putchar(48);
else print(dis[i]);
putchar(10);
}
return 0;
}
VIJOS 1392 拼拼图的小杉
题目
歹徒告诉小杉,他正在寻找的拼图块其实可以拼成
N
N
N个有顺序的完整的拼图。
每个完整的拼图由若干个拼图块组成。
歹徒要求小杉把拼图按拼出的顺序划分成
M
M
M个集合,一个拼图集合由若干个完整的拼图组成,并且总的拼图块的数目不超过
T
T
T。并且,构成集合的拼图是不能交叉的。
小杉要找出划分成
M
M
M个集合后,
M
M
M个集合中最多能有多少个完整的拼图。
分析
那么可以发现这道题是一个动态规划的题目
对于每种拼图,那么要记录它所处的集合是哪一个及该集合的拼图数,那么可以得到
f
[
i
]
=
m
i
n
(
f
[
j
−
1
]
+
a
[
i
]
)
(
1
≤
j
≤
i
,
并
且
符
合
条
件
)
f[i]=min(f[j-1]+a[i])(1\leq j\leq i,并且符合条件)
f[i]=min(f[j−1]+a[i])(1≤j≤i,并且符合条件),详见代码
代码
#include <cstdio>
#define rr register
using namespace std;
int n,m,summ,a[1001];
struct rec{
int now,sum;
bool operator <(const rec &t)const{
if (now!=t.now) return now<t.now;
return sum<t.sum;
}
rec operator +(const int t)const{
if (sum+t<=summ) return (rec){now,sum+t};//没有超过单个集合的限制
else return (rec){now+1,t};//否则要新增一个集合
}
}f[1001];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
return ans;
}
signed main(){
n=iut(); m=iut(); summ=iut(); f[0].now=1;
for (rr int i=1;i<=n;++i) a[i]=iut();
for (rr int i=1;i<=n;++i){
f[i]=f[i-1]+a[i];//显而易见从上一个块递推过来
for (rr int j=i-1;j;--j){
rr rec t=f[j-1]+a[i];//维护最小值
if (t<f[j]) f[j]=t;
}
}
for (rr int i=n;i;--i)
if (f[i].now<=m) return !printf("%d",i);//从后往前推
}
后续
其实纪中也有后三道题目