1. 程式人生 > >【比賽報告】2018.8.7集訓 NOIP練習賽卷十

【比賽報告】2018.8.7集訓 NOIP練習賽卷十

A.函式

題目連結 問題描述

對於一個整數,定義 f(x)為他的每個數位的階乘的乘積。例如 f(135)=1! * 3! * 5! = 720。給出一個數 a(可以包含字首零), a 滿足他的至少一個數位大於 1。我們要求出最 大 的整數 x,其中 x 不含 0 或 1,並且滿足 f(a) = f(x)。

輸入

第一行一個整數 n,表示 a 的長度。 接下來一個整數 a。

輸出

一行一個整數 x 表示答案。 【輸入樣例 1】 4 1234 【輸出樣例 1】 33222 【樣例 1 說明】 1! * 2! * 3! * 4! = 3! * 3! * 2! * 2! * 2! 【輸入樣例 2】 2 03 【輸出樣例】

3 【樣例 2 說明】 0! * 3! = 3! 【資料範圍】 對 30%的輸入資料 : n≤2 對 100%的輸入資料 : n≤15

考場上居然把這題打掛了氣死了……教練都說這題隨便亂搞都能過的…… 可以知道2~9的階乘可以寫成2,3,5,7的階乘的組合……於是我也來亂搞了

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
char a[20];
int num[20];
int zs[4]={2,3,5,7};
vector<int>vx[10];
priority_queue<int>ans;
void Init()
{
	vx[2].push_back(2);
	vx[3].push_back(3);
	vx[4].push_back(3);vx[4].push_back(2);vx[4].push_back(2);
	vx[5].push_back(5);
	vx[6].push_back(5);vx[6].push_back(3);
	vx[7].push_back(7);
	vx[8].push_back(7);vx[8].push_back(2);vx[8].push_back(2);vx[8].push_back(2);
	vx[9].push_back(7);vx[9].push_back(3);vx[9].push_back(3);vx[9].push_back(2);
}
int main()
{
	int n;
	Init();
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		num[i]=a[i]-'0';
		if(num[i]==0||num[i]==1)continue;
		for(int j=0;j<vx[num[i]].size();j++)ans.push(vx[num[i]][j]);
    }
    while(!ans.empty())
    {
    	int tmp=ans.top();ans.pop();
    	cout<<tmp;
    }
    puts("");
	return 0;
}

B.箱子

在這裡插入圖片描述 在這裡插入圖片描述

用 dist[position][box1][box2][box3]記錄人在 position,箱子 1 在 box1,箱子 2 在box2,箱子 3 在 box3 位置時最少需要花費的步數。 Bfs 一遍即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6e6+10;
bool vis[10][10];//標記是否可行 
char s[10];//讀入每一行 
int nm[110][3];//新建圖 
int R,C;//r行c列 
int sx,sy;//人的位置 
int cnt1,cnt2,cnt3;
//cnt1用來編號,cnt2cnt3為箱子和按鈕的編號 
int nextx[4]={1,-1,0,0};
int nexty[4]={0,0,1,-1};//列舉四個方向 
int pos[10][10];//每個位置的編號 
int num[10][10];//地圖的數字 
int box[5],button[5];//盒子與按鈕的編號 
int f[50][50][50][50];//f[i][j][k][l]表示人在i,三個盒子在j,k,l時的最小步數 
int c[N][5];//c模擬佇列 
int main()
{
	//freopen("in.txt","r",stdin);
	scanf("%d%d",&R,&C);///R行C列 
	memset(nm,0,sizeof(nm));
	memset(f,255,sizeof(f));
	memset(num,0,sizeof(num));
	memset(box,0,sizeof(box));
	memset(button,0,sizeof(button));
	memset(vis,true,sizeof(vis));
	cnt1=cnt2=cnt3=0;
	for(int i=1;i<=R;i++)
	{
		scanf("%s",s);
		for(int j=1;j<=C;j++)
		{
			pos[i][j]=++cnt1;//給每個點標號 
			nm[cnt1][1]=i;nm[cnt1][2]=j;
			num[i][j]=s[j-1]-'0';//轉成數字 
			if(num[i][j]==2)box[++cnt2]=pos[i][j];
			if(num[i][j]==3)button[++cnt3]=pos[i][j];
			if(num[i][j]==4)sx=i,sy=j,num[i][j]=0;
		}
	}
	f[pos[sx][sy]][box[1]][box[2]][box[3]]=0;//初始狀態 
	c[1][0]=pos[sx][sy];//人的編號 
	c[1][1]=box[1];
	c[1][2]=box[2];
	c[1][3]=box[3]; //三個箱子 
	for(int i=1;i<=R;i++)
	for(int j=1;j<=C;j++)
	    if(num[i][j]==1)vis[i][j]=false; 
	for(int l=1,k=1;l<=k;l++)
	{
		int man=c[l][0],box1=c[l][1],box2=c[l][2],box3=c[l][3];
		int xman=nm[man][1],yman=nm[man][2];
		int xbox1=nm[box1][1],ybox1=nm[box1][2];
		int xbox2=nm[box2][1],ybox2=nm[box2][2];
		int xbox3=nm[box3][1],ybox3=nm[box3][2];
		vis[xbox1][ybox1]=vis[xbox2][ybox2]=vis[xbox3][ybox3]=false;
		if(box1==button[1]&&box2==button[2]&&box3==button[3])
		{
			printf("%d\n",f[man][box1][box2][box3]);
			return 0;
		}
		int step=f[man][box1][box2][box3];
		for(int i=0;i<4;i++)
		{
			int nxman=xman+nextx[i];
			int nyman=yman+nexty[i];//拓展一步 
			if(nxman>=1&&nxman<=R&&nyman>=1&&nyman<=C){
			    if(vis[nxman][nyman]&&f[pos[nxman][nyman]][box1][box2][box3]==-1)//這種狀態沒有走過 
			    {
				    f[pos[nxman][nyman]][box1][box2][box3]=step+1;
				    c[++k][0]=pos[nxman][nyman];
				    c[k][1]=box1;
				    c[k][2]=box2;
				    c[k][3]=box3;
			    }
			    int q[4]; 
				q[1]=box1;q[2]=box2;q[3]=box3;
			    if(pos[nxman][nyman]==q[2])swap(q[1],q[2]);
			    if(pos[nxman][nyman]==q[3])swap(q[1],q[3]);//避免狀態重複
			    if(pos[nxman][nyman]==q[1])
			    {
				    int nnxman=xman+2*nextx[i];
				    int nnyman=yman+2*nexty[i];//沿當前方向再向前一步 
				    if(nnxman>=1&&nnxman<=R&&nnyman>=1&&nnyman<=C&&vis[nnxman][nnyman])
				    {
					    q[1]=pos[nnxman][nnyman];
					    for(int a=1;a<=3;a++)
					    for(int b=a+1;b<=3;b++)
					        if(q[b]!=0&&q[a]>q[b])swap(q[a],q[b]);
					    if(f[pos[nxman][nyman]][q[1]][q[2]][q[3]]==-1)
					    {
						    f[pos[nxman][nyman]][q[1]][q[2]][q[3]]=step+1;
						    c[++k][0]=pos[nxman][nyman];
						    c[k][1]=q[1];
						    c[k][2]=q[2];
						    c[k][3]=q[3];
					    }
				    }
			    } 
			}
			vis[xbox1][ybox1]=vis[xbox2][ybox2]=vis[xbox3][ybox3]=true;
	    }
	}
}

總結

bfs問題,最關鍵的是建模

題目描述

給你一個無向帶權連通圖,每條邊是黑色或白色。讓你求一棵最小權的恰好有need條白色邊的生成樹。 題目保證有解。 輸入

第一行V,E,need分別表示點數,邊數和需要的白色邊數。 接下來E行 每行s,t,c,col表示這邊的端點(點從0開始標號),邊權,顏色(0白色1黑色)

輸出

一行表示所求生成樹的邊權和。

樣例輸入

2 2 1 0 1 1 1 0 1 2 0

樣例輸出

2

資料規模和約定

10%:V<=10 30%:V<=15 100%:V<=50000,E<=100000 所有資料邊權為[1,100]中的正整數。

題解

考慮如何才能讓白邊顯得更(不)重要,即在每條白邊上(加上)減去一個值。 我們可以二分這個值,然後用尋常方法做最小生成樹。 統計在此最小生成樹裡有多少白邊。 然後我們就可以找到一個合適的值,帶這個權做一次最小生成樹。 在計算答案的時候把這些值補償回去就做完了。

#include<cstdio>
#include<vector>
#include<algorithm>
#define PB(v) push_back(v)
using namespace std;
const int N=5e4+10;
const int M=1e5+10;
int n,m,k;
struct Edge{
	int u,v,prew,noww,color;//端點,最初邊權,修改後的邊權,邊的顏色 
	bool operator <(const Edge rhs)const{
	return noww<rhs.noww||(noww==rhs.noww&&color<rhs.color);}
};
vector<Edge>w;//白邊 
vector<Edge>b;//黑邊 
int set[N];
int sum,cnt;
int findset(int x)
{
	return set[x]==x?x:set[x]=findset(set[x]);
}
bool check(int x)
{
	for(int i=0;i<w.size();i++)
	w[i].noww=w[i].prew+x;//修改白邊邊權 
	Edge edge[M];
	merge(w.begin(),w.end(),b.begin(),b.end(),edge);//修改後白邊和黑邊按順序加入容器中
	for(int i=0;i<n;i++) 
	set[i]=i;
	sum=cnt=0;
	for(int i=0;i<m;i++)//跑最小生成樹 
	{
		int fu=findset(edge[i].u);
		int fv=findset(edge[i].v);
		if(fu==fv)continue;
		set[fu]=fv;
		sum+=edge[i].noww;
		if(!edge[i].color)cnt++;
	}
	return cnt>=k;//白邊數目與需要數目 
}
int main()
{
	//freopen("in.txt","r",stdin);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i<m;i++)
	{
		Edge e;
		scanf("%d%d%d%d",&e.u,&e.v,&e.prew,&e.color);
		e.noww=e.prew;
		if(e.color==0)w.PB(e);
		else b.PB(e);
	}
	sort(b.begin(),b.end());
	sort(w.begin(),w.end());
	int l=-101,r=101;
	while(l<r-1)//二分修改的邊權 
	{
		int mid=l+r>>1;
		if(check(mid))l=mid;//白邊多了說明加的數過小 
		else r=mid;//白邊少了說明加的數過大 
	}
	check(l);
	printf("%d\n",sum-l*k);
	return 0;
}

比賽總結

這套題最關鍵的就是bfs建模和二分邊權的生成樹