1. 程式人生 > >【日常刷題】NOIP練習題題解

【日常刷題】NOIP練習題題解

NOIP練習題題解

1.小X與位運算

題目描述
自從上次小X 搞定了完美數之後,他最近在研究一項和計算機密切相關的黑科技。要知道在計算機的內部,資料都是以二進位制的形式來進行儲存的,而它使用的計算方法也和我們平時的加減乘除四則運算 有所不同,它使用的是位運算。那什麼是位運算呢? 基礎位運算有三種符號,分別是 and,or,xor(分別對應 pascal 中的 and,or,xor 三種運算子 號)。
以 and 為例,兩個二進位制數在做 and 運算時,分別對兩個二進位制數的每一位做 and 運算。而對每一 位做and 運算時,遵守以下規則:只有當兩個數的這一位都是 1 時,運算結果才為 1,否則就是 0。例如 1101 和10101 做and 運算之後結果為101(高位不足用0 補齊,最後結果忽略前導0)。 通俗點講 and 運算就是按位做乘法,即將兩個二進位制數從高位到低位依次對齊,然後每一位上對齊 的兩個數相乘即得到這一位的結果 。我們可以列一個簡單的例子來說明個and運算:01101 and 10101 = 00101。
而or,xor 的運算方法類似,唯一不同的是在對每一位做運算時遵循的方法不同。 or 運算遵守以下規則:只有當兩個數的這一位都是 0 時,運算結果才為 0,否則就是 1。例如1101or10101=11101。
xor 運算遵守以下規則:只有當兩個數的這一位相同時,運算結果才為0,否則就是1。例如1101xor10101=11000 。
小 X想知道兩個很大很大的二進位制數,在做完位運算之後, 最後的結果是什麼。而小X 自己無法知道正確答案是什麼,他只好求助於你來幫助他解決這個問題。
輸入格式


輸入資料第一行是一個字串,由字元0 和1組成,表示一個二進位制數。 第二行也是一個字串,由字元0 和1組成,同樣表示一個二進位制數。 第三行還是一個字串,一定是and,or,xor三個中一種,表示運算子號。 注意輸入的二進位制數沒有前導零,字元個數可能會超過255 個。
輸出格式
輸出一行一個字串,由字元0和1 組成,表示最後運算得到的二進位制數。 注意輸出的二進位制數不能帶有前導零,即輸出的第一個字元不能為0。
樣例資料
input
110100
11001
or
output
111101

題解:
讀入兩個字串s1和s2,設長度分別是len1和len2,則:
1.len1=len2,直接進行運算
2.len1<len2,第0~len2-len1-1位是第二個字串與0進行邏輯運算,接下來再逐漸配對。
3.len1>len2,與2相反。

CODE

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200000

string s;
char s1[MAXN];
char s2[MAXN];
int ans[MAXN],len1,len2,Max;

void work_xor()
{
    for (int i=0;i<Max;++i)
    {
        int det=abs(len1-len2);
    	if (len1!=Max && len2==Max)
    	{
    		if (i<len2-
len1) ans[i]=0^int(s2[i]-48); else ans[i]=int(s1[i-det]-48)^int(s2[i]-48); } if (len1==Max && len2!=Max) { if (i<len1-len2) ans[i]=0^int(s1[i]-48); else ans[i]=int(s1[i]-48)^int(s2[i-det]-48); } if (len1==Max && len2==Max) ans[i]=int(s1[i]-48)^int(s2[i]-48); } } void work_or() { for (int i=0;i<Max;++i) { int det=abs(len1-len2); if (len1!=Max && len2==Max) { if (i<len2-len1) ans[i]=0|int(s2[i]-48); else ans[i]=int(s1[i-det]-48)|int(s2[i]-48); } if (len1==Max && len2!=Max) { if (i<len1-len2) ans[i]=0|int(s1[i]-48); else ans[i]=int(s1[i]-48)|int(s2[i-det]-48); } if (len1==Max && len2==Max) ans[i]=int(s1[i]-48)|int(s2[i]-48); } } void work_and() { for (int i=0;i<Max;++i) { int det=abs(len1-len2); if (len1!=Max && len2==Max) { if (i<len2-len1) ans[i]=0&int(s2[i]-48); else ans[i]=int(s1[i-det]-48)&int(s2[i]-48); } if (len1==Max && len2!=Max) { if (i<len1-len2) ans[i]=0&int(s1[i]-48); else ans[i]=int(s1[i]-48)&int(s2[i-det]-48); } if (len1==Max && len2==Max) ans[i]=int(s1[i]-48)&int(s2[i]-48); } } int main() { freopen("bignum.in","r",stdin); freopen("bignum.out","w",stdout); cin>>s1; cin>>s2; cin>>s; len1=strlen(s1); len2=strlen(s2); Max=max(len1,len2); if (s=="xor") work_xor(); if (s=="and") work_and(); if (s=="or") work_or(); for (int i=0;i<Max;++i) if (ans[i]==1) { for (int j=i;j<Max;++j) cout<<ans[j]; break; } fclose(stdin); fclose(stdout); return 0; }

2.小x與機器人

題目描述
小 X 最近對戰勝韓國圍棋大神李世石的 AlphaGo 很感興趣,所以小 X 自己寫了一個叫做 BetaGo 的人工智慧程式(簡稱 AI),這個 BetaGo 會做什麼呢? 小 X 首先想要讓 BetaGo 做到自己在棋盤上落子,這一點 AlphaGo 是由程式設計師來完成的。
小 X 的 設想是這樣的:在棋盤的邊框上放置一個小機器人,這個小機器人會沿著棋盤的邊框移動到最接近落子點的位置,然後伸出它的機械臂將棋子放到棋盤上。這裡面最關鍵的一步是如何讓小機器人在棋盤的邊框上沿著最短的路徑移動,小 X 想請你幫他編個程式解決這個問題。眾所周知,圍棋棋盤大小為 19 × 19(如下圖所示),圖中加粗的一圈即為邊框。我們用一對整數 (x, y) 來表示棋盤上第 x 條橫線(從下往上數)與第 y 條豎線(從左往右數)的交叉點,如上圖中邊框上的 A 點用(6,1)表示,B 點用(10,19)表示,小機器人初始時放置在 (x1, y1) 這個位置上,它想要移動到 (x2, y2) 這個位置上。
(x1, y1)和(x2, y2) 一定是棋盤邊框上的交叉 點 每一步小機器人可以從當前位置移動到相鄰(上下左右)的某個位置上,即每次可以從 (x, y) 移 動到 (x - 1, y)、(x + 1, y)、(x, y - 1)、(x, y + 1) 四個位置中的一個,但是它不能走出或走進棋盤, 也就是說它只能沿著棋盤的邊框移動到相鄰位置,這就意味著任一時刻相鄰位置都恰好只有兩個。 BetaGo 會告訴小機器人最少需要走多少步,但小 X 還是很擔心 BetaGo 有的時候會失控,從而告訴他一個錯誤值。為此小 X 只好求助你,希望你編一個程式計算從 (x1, y1) 沿著棋盤的邊框移動到 (x2, y2) 最少需要走多少步。上圖中從 A 點(6,1)移動到 B 點(10,19)最少需要走 32 步,移動路線是: (6,1)→(5,1)→(4,1)→(3,1)→(2,1)→(1,1)→(1,2)→(1,3)→…… →(1,19)→(2,19)→……→(10,19)。
輸入格式
輸入資料僅有一行包含四個用空格隔開的正整數表示 x1, y1, x2, y2。
資料保證 (x1, y1),(x2, y2) 一定是棋盤邊框上的交叉點。
輸出格式
輸出一行包含一個整數 ans,表示小機器人從 (x1, y1) 移動到 (x2, y2) 的最少步數。
樣例資料
input

6 1 10 19
output
32

本題難點在於讀懂題目:機器人只會在邊框移動;最後的終點也只會在邊框上出現。
用DFS+記憶化搜尋預處理每一個點的最短路徑,然後直接輸出即可。

CODE

#include<bits/stdc++.h>
using namespace std;

int ans;
int x,y,fx,fy,ax,ay,st=1e9;
int f[25][25];
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};

inline int dis(int a,int b,int c,int d){
	return abs(a-c)+abs(b-d);
}

inline bool check(int tx,int ty){
	return (tx==1 || tx==19 || ty==1 || ty==19) && (tx>=1 && tx<=19 && ty>=1 && ty<=19);
}

inline bool canget(int tx,int ty){
	return tx==fx || ty==fy;
}
void dfs(int lx,int ly,int sum)
{
	int nx,ny;
	if (sum>=f[lx][ly]) return;
	f[lx][ly]=sum;
	if (canget(lx,ly) && dis(lx,ly,fx,fy)<ans) ans=dis(x,y,fx,fy),st=sum+dis(x,y,fx,fy); 
	if (canget(lx,ly) && dis(lx,ly,fx,fy)==ans) st=min(st,st=sum+dis(x,y,fx,fy));
	for (int i=0;i<4;++i)
	{
		nx=lx+dx[i];
		ny=ly+dy[i];
		if (check(nx,ny)==false) continue; 
		dfs(nx,ny,sum+1);
	}
	return;
}
int main()
{
	freopen("betago.in","r",stdin);
	freopen("betago.out","w",stdout);
	
	memset(f,127,sizeof(f));
	cin>>x>>y>>fx>>fy;
	ans=1e9;
	dfs(x,y,0);
	ans=1e9;
	cout<<f[fx][fy];
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

3.賽車

題目描述
小x為了平復自己悲憤的心情,參加了F7賽車決賽的解說工作。作為一位優秀的主持人,他想要了解一下參加決賽的N位選手的情況。經過一番努力,他找到了各位選手前幾站比賽的成績。
決賽就要開始了,比賽規定:第一個到達終點的得到N分,第二個到達終點的得到N-1分,以此類推…最後一個到達終點的得到1分。而且不會有兩位選手同時到達終點。
小x非常忙,所以他想請你幫他統計一下有多少選手有可能成為總冠軍(之前的成績+決賽成績=總成績,總成績最高者為總冠軍,總冠軍可能有多位)。
輸入格式
第一行一個正整數N(3≤N≤300000),代表參加決賽的選手個數。
接下來N行,每行一個正整數Bi,代表第i位選手之前比賽的成績。
輸出格式
一行一個正整數,代表有可能成為總冠軍的選手個數。
樣例資料
input1

3
8
10
9
output1
3
input2
5
15
14
15
12
14
output2
4

題解
對於暴力方法,我們可以這麼做:
要使這個i為冠軍,必須不惜一切代價湊足一種情況下使得其他數都小於這個數字,那麼必然需要除了他以外的每一個選手,原來的值越大,加上的值越小。這是一個基於貪心的策略。
顯然,列舉每一個人再加以此會超時,我們可以這麼做:
設初始序列排序後有一個遞減序列: a [ 1 ] , a [ 2 ] , a [ 3 ] . . . a [ n ] a[1],a[2],a[3]...a[n]
在保證這串序列是不上升的情況下,分別加上一個數: a [ 1 ] + 1 , a [ 2 ] + 2 , . . . , a [ n ] + n a[1]+1,a[2]+2,...,a[n]+n
用一個字首和lefmax[i]記錄1-i的最大值,用一個字尾和rightmax記錄i-n的最大值。
對於每一個i,顯然如果取n會出現這樣的情況: a [ 1 ] + 1 , a [ 2 ] + 2 , . . . , a [ i ] + n , a [ i + 1 ] + i , a [ i + 2 ] + i + 1 , . . . , a [ n ] + n 1 a[1]+1,a[2]+2,...,a[i]+n,a[i+1]+i,a[i+2]+i+1,...,a[n]+n-1
那樣直接用字首和與字尾和-1與a[i]+n判斷是否合法即可。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 302000

int mark[MAXN],sum[MAXN];
int leftmax[MAXN],rightmax[MAXN];

inline void read(int &x)
{
	int s=0;char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	x=s;return;
}

inline bool cmp(int a,int b){
	return a>b;
}

int main()
{
	freopen("racing.in","r",stdin);
	freopen("racing.out","w",stdout);
	int n,ans=0;
	read(n);
	for (int i=1;i<=n;++i) read(mark[i]);
	sort(mark+1,mark+n+1,cmp);
	for (int i=1;i<=n;++i) 
	{
		sum[i