noip2013題解 普及組
這次整體題目個人認為不是非常難,但是想取得高分並不容易。
接下來是我的個人分析(包含ak程式碼)
1.計數問題
首先,第一眼看到這個題目我就驚呆了。因為做過。看了看資料範圍,比較小,每個數字最多6位,1000000個,6000000次秒過是沒有太大的問題的。
只要將每一個數字分離即可。
#include<stdio.h> #include<iostream> using namespace std; const int MAX_P = 10; int ans = 0; int n,x; void init() { scanf("%d %d",&n,&x); //讀入 } void makenx(int t) { while (t) { if (t%10==x) ans++; //如果這個位置上的數字恰好為x ans方案數+1 t/=10; } } void work() { int i; for (i=1;i<=n;i++) makenx(i); //從1~n } void put() { printf("%d",ans); } int main() { freopen("count.in","r",stdin); freopen("count.out","w",stdout); init(); work(); put(); return 0; }
2.表示式求值
看到題目,我又驚呆了。還是做過。
可以用棧,但是我平常不習慣,而且用得不熟,畢竟是考試,就怕溢位。
首先我們將問題分解,就是先把所有的乘法算出,再算加法。每次mod1k
考慮特殊情況
1.第一個數字前沒有符號
2.加好後面的數字沒有符號
3.數字總長度不能開10w陣列
#include<stdio.h> #include<iostream> #include<string.h> using namespace std; const int MAX_N = 5000000; const int INF = 10000; char s[MAX_N]; int ans = 0; int length; void init() { gets(s); //讀取字串 length=strlen(s); //測長度 } void work() { int i,j,k,tmp,ctmp; //tmp表示當前數字 ctmp表示乘法當中的數字 i=0; while (i<length) { ctmp = 0; tmp = 0; //初始化 j=i; while (j<length&&s[j]>='0'&&s[j]<='9') //如果沒有掃描完 並且是數字 { tmp=tmp*10+(s[j]-'0'); //*10+ 把字串還原為數字 j++; } tmp%=INF; //mod 1k ctmp=tmp; //乘法第一項就是tmp while (j<length&&s[j]=='*') //從後面起將一個數字和一個字元作為一組,掃描了數字,如果後面是* 繼續乘法 { //否則 就是+(題目保證) k=j+1; tmp = 0; while (k<length&&s[k]>='0'&&s[k]<='9') { tmp = tmp*10+(s[k]-'0'); //*10+ k++; } tmp%=INF; //注意先餘 否則int_max*1k會大於int_max ctmp = ctmp*tmp%INF; j=k; } ans = (ans+ctmp)%INF; //作為一個加法的結束 i=j+1; } } void put() { printf("%d",ans); } int main() { freopen("expr.in","r",stdin); freopen("expr.out","w",stdout); init(); work(); put(); return 0; }
3.小朋友的數字
看到題目,依然驚呆,不過可惜沒ac,誒,高精度當時考慮了 當時覺得沒必要,但是。。不說了,全是淚。
首先由貪心思想做最大子矩陣。
不多說。
考慮一下 如果每個數都是10ww 那麼特徵值最大n^2*10ww = 10^21
加上分數大概在10^20左右
long long 和int64爆了。
所以要用兩個10億進位制。見程式碼
#include<stdio.h> #include<iostream> using namespace std; const int MAX_N = 1000001; const long long INF = 1000000000000; int num[MAX_N]; long long speasnum[MAX_N][2]; //[2]表示兩位 每一位儲存後面12位數字 long long marknum[MAX_N][2]; long long sans[2]; long long mans[2]; long long ans[2]; int N;int P; void init() { int i; scanf("%d %d",&N,&P); for (i=1;i<=N;i++) scanf("%d",&num[i]); //讀入資料 } void finding_speasnum() { int i; long long stmp[2]; stmp[0] = num[1];//stmp表示目前的最大和 stmp[1] = 0; //這句不能少,區域性變數他的最高位[1]雖然為0 但是不能自行初始化 speasnum[1][0]=num[1]; //第一個人的特徵值等於自己 sans[0] = num[1]; //最大特徵值等於第一個人 for (i=2;i<=N;i++) { if (stmp[0]<=0&&stmp[1]<=0) stmp[0] = 0,stmp[1]=0; //如果stmp就是目前連續的最大和<0 更新為0 stmp[0]+=num[i]; stmp[1]+=stmp[0]/INF; stmp[0]%=INF; //加上這樣一個數字 /*兩位大高精度的經典程式碼 x[2] y[2] x[0]y[0]為低位 x[1]y[1]為高位 將y+x的值儲存到x中 x[0]+=y[0] 逐位加 x[1]+=y[1] x[1]+=x[0]/INF x[0]%=INF 進位 */ if (sans[1]<stmp[1]||(sans[1]==stmp[1]&&sans[0]<stmp[0])) //如果答案sans可以更新 就更新為stmp /*兩位大高精度比大小的經典程式碼 x[2] y[2] x[0]y[0]為低位 x[1]y[1]為高位 判斷x是否大於y if (x[1]>y[1]||(x[1]==y[1]&&x[0]>y[0])) */ { sans[0] = stmp[0]; sans[1] = stmp[1]; } speasnum[i][0]=sans[0]; speasnum[i][1]=sans[1]; //將目前的最大欄位和放到特徵值中 } } void finding_marknum() { int i; marknum[1][0]=speasnum[1][0]; //第一個人的分數等於特徵值 marknum[1][1]=speasnum[1][1]; ans[0]= marknum[1][0]; //答案目前為第一個人的分數 ans[1]= marknum[1][1]; ans[1]+=ans[0]/INF; ans[0]%=INF; mans[0] = marknum[1][0]+speasnum[1][0]; //分數和特徵值的和的最大值 mans[1] = marknum[1][1]+speasnum[1][1]; mans[1]+= mans[0]/INF; mans[0]%= INF; long long newi[2]; for (i=2;i<=N;i++) { marknum[i][0] =mans[0]; marknum[i][1] =mans[1]; //分數為目前最大值 newi[0]=marknum[i][0]+speasnum[i][0]; //這個人的分數和特徵的和 newi[1]=marknum[i][1]+speasnum[i][1]; newi[1]+=newi[0]/INF; newi[0]%=INF; if (newi[1]>mans[1]||(newi[1]==mans[1]&&newi[0]>mans[0])) { //更新答案 mans[0]=newi[0]; mans[1]=newi[1]; } if (marknum[i][1]>ans[1]||(marknum[i][1]==ans[1]&&marknum[i][0]>ans[0])) //更新ans { ans[0]=marknum[i][0]; ans[1]=marknum[i][1]; } } } void work() { finding_speasnum(); //尋找特徵值 finding_marknum(); //尋找分數 } void put() { long long putans = ans[1]%P; //慣用的方法,就是大餘單,如果不會自行百度 long long p = INF; if (ans[0]<0) {ans[0]=-ans[0];printf("-");} //這裡先取絕對值,安全一點,還好今年沒有-0的測試點 while(p>1) { putans*=10; putans%=P; p/=10; } putans+=ans[0]; putans%=P; cout<<putans; //這段經典程式碼最好背下來 } int main() { freopen("number.in","r",stdin); freopen("number.out","w",stdout); init(); work(); put(); return 0; }
4.車站分級
首先由貪心思想。
找到兩個車站的值 如果a路線中包含b 且a不停 b停車 就用a更新b
但是有反例:
5 2
1 5
2 3
輸出:2
答案:3
所以很快想到拓撲排序:
如果a路線中包含b 且a不停 b停車 就存在一條邊權為1的有向邊(a,b)
我們就用拓撲排序的思想解決問題。
但是很快發現M=1000 N=1000
構圖需要M*(N^2)的時間承受不了。
也就是說如果有一條線路中包含8個車站,那麼有3個車站停,5各車站不停
就產生3*5條邊
我們很快又想到並查集,但是解決不了,不過可以借鑑思想。
這5個車站都聯通另外一個bt車站權值為0
而bt車站到另外3個要停的車站邊權為1
就完成構圖。
我的分析應該是產生2*N^2條邊。
就完全沒壓力了
這次最後一題也是這麼多年以來,少於100行的為數不多的幾次
第三題反而長一點。
#include<stdio.h>
#include<iostream>
#include<vector>
#include<memory.h>
using namespace std;
const int MAX_N = 2002;
int N,M;int P;
int s[MAX_N]; //how many stations in route i
int level[MAX_N]; //level
int infor[MAX_N][MAX_N]; //info about route i
vector<int> d[MAX_N]; //don't stop here
vector<int> p[MAX_N]; //stop here
int adj[MAX_N][MAX_N];
int con[MAX_N];
void init()
{
int i,j,k;
scanf("%d %d",&N,&M);
memset(adj,-1,sizeof(adj));
P=N;
for (i=1;i<=M;i++)
{
scanf("%d",&s[i]); //讀入
for (j=1;j<=s[i];j++)
scanf("%d",&infor[i][j]);
if (infor[i][s[i]]-infor[i][1]+1==s[i]) continue;
//如果在這條線路當中最後一個車站-第一個車站序號+1=總的經過車站數量,也就沒有不停的車站,就不存在做的可能
//而題目保證一條線路中至少有2個停的車站,所以放心
for (j=infor[i][1],k=1;j<=infor[i][s[i]];j++)
{
if (j==infor[i][k]) {p[i].push_back(j);k++;}
else {d[i].push_back(j);} //掃描那些節點在d停車的節點中 那些在p不停的當中
}
P++; //產生bt節點
for (j=0;j<d[i].size();j++)
adj[d[i][j]][P]=0,con[P]++; //構造(Dij,P)有向邊 con[P]入度++
for (j=0;j<p[i].size();j++)
adj[P][p[i][j]]=1,con[p[i][j]]++;
}
for (i=1;i<=P;i++)
level[i]=1;//初始化 level至少為1
}
int find_zero()
{
int i;
for (i=1;i<=P;i++)
if (con[i]==0) return i; //尋找入度為0的節點
return -1;
}
void work()
{
int i,j,k;
for (i=1;i<=P;i++)
{
k=find_zero();
if (k==-1) return ; //如果沒有入度為0的頂點 說明做完了
for (k<=N?j=N+1:j=1;k<=N?j<=P:j<=N;j++)
//完全可以寫for (j=1;j<=P;j++)
//不過這是常數級別優化
//因為根據題意可知
//如果存在邊 (x,y) 則x∈車站T集合 y∈bt節點集合
//否則 y∈車站T集合 x∈bt節點集合
if (adj[k][j]!=-1) //-1表示沒有變
{
level[j]=max(level[j],level[k]+adj[k][j]); //求最長路
adj[k][j]=false; //刪除這條邊
con[j]--; //入度--
}
con[k]=-1; //標記已經做過這個頂點
}
}
void put()
{
int i;
int ans = 0;
for (i=1;i<=P;i++)
ans = max(ans,level[i]); //尋找最大等級
printf("%d",ans);
}
int main()
{
freopen("level.in","r",stdin);
freopen("level.out","w",stdout);
init();
work();
put();
return 0;
}