【比賽報告】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 【輸出樣例】
考場上居然把這題打掛了氣死了……教練都說這題隨便亂搞都能過的…… 可以知道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建模和二分邊權的生成樹