1. 程式人生 > >noip2013題解 普及組

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;
}