1. 程式人生 > >2018年10月31日普級組

2018年10月31日普級組

前言

賽後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 玩詐欺的小杉

題目

在小杉的面前有一個NNMM列的棋盤,棋盤上有N×MN\times M個有黑白棋的棋子(一面為黑,一面為白),一開始都是白麵朝上。可以對任意一個格子進行至多一次的操作(最多進行N×MN\times 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 想越獄的小杉

分析

然而這道題就是單源最短路徑的最短邊最長問題,很容易得到鬆弛操作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 拼拼圖的小杉

題目

歹徒告訴小杉,他正在尋找的拼圖塊其實可以拼成NN個有順序的完整的拼圖。
每個完整的拼圖由若干個拼圖塊組成。
歹徒要求小杉把拼圖按拼出的順序劃分成MM個集合,一個拼圖集合由若干個完整的拼圖組成,並且總的拼圖塊的數目不超過TT。並且,構成集合的拼圖是不能交叉的。
小杉要找出劃分成MM個集合後,MM個集合中最多能有多少個完整的拼圖。

分析

那麼可以發現這道題是一個動態規劃的題目
對於每種拼圖,那麼要記錄它所處的集合是哪一個及該集合的拼圖數,那麼可以得到f[i]=min(f[j1]+a[i])(1ji,)f[i]=min(f[j-1]+a[i])(1\leq j\leq 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);//從後往前推
}

後續

其實紀中也有後三道題目