厦门大学“网宿杯“17届程序设计竞赛决赛(同步赛) #题解 #题目都超有趣呀
文章目錄
- A.這波啊,這波是.....
- B.李在贛神魔
- C.電競希金斯
- D.財富密碼
- E.蕪湖起飛
- F.這題多撈啊
- G.正方形打野
- H.時間管理
A.這波啊,這波是…
鏈接:https://ac.nowcoder.com/acm/contest/5945/A
來源:牛客網
題目描述
“這波啊,這波是肉蛋蔥雞!”
打出口令即可領取簽到獎勵。
輸入描述:
沒有輸入。
輸出描述:
見樣例輸出。
示例1
輸入
non
輸出
roudancongji
說明
如果你不知道題目在講什么也沒關系,你只需要輸出樣例即可通過本題。
思路:
簽到題,看懂提就行
代碼:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1010; int a[maxn]; int main() {cout << "roudancongji" << endl;return 0; }B.李在贛神魔
鏈接:https://ac.nowcoder.com/acm/contest/5945/B
來源:牛客網
題目描述
我們要做一個旋轉木馬! 輸入一個n\times nn×n的字符矩陣,將其順時針旋轉90度后輸出。
輸入描述:
每個測試點僅包含一組輸入數據。
第一行一個整數n(1 \leq n \leq 1000)n(1≤n≤1000),表示矩陣大小。
接下來n行,每行一個長度為n的字符串,僅包含小寫字母,表示這個矩陣。
輸出描述:
輸出順時針旋轉90度后的矩陣,行末不要出現多余空格。
示例1
輸入
3
aaa
bbb
ccc
輸出
cba
cba
cba
思路:
也是個簽到題,不需要任何算法知識背景,不多說了
代碼:
#include <stdio.h> main() {int n,i,j;char a[1005][1005],b[1005][1005];scanf("%d",&n);for(i=0;i<n;i++){scanf("%s",a[i]);}for(i=0;i<n;i++){for(j=0;j<n;j++){b[j][n-i-1]=a[i][j];}}for(i=0;i<n;i++){printf("%s\n",b[i]);} }C.電競希金斯
鏈接:https://ac.nowcoder.com/acm/contest/5945/C
來源:牛客網
題目描述
大司馬綽號“電競希金斯”,所以他的幾何非常好。他發明的“馬氏幾何”多次挑戰牛頓和愛因斯坦的理論,連奧沙利文都直呼內行。
本題就是一道關于計算幾何的題目。
給定一條直線ax+by+c=0,請以編號從小到大的順序輸出這條直線經過的象限。
注意,x軸和y軸不屬于任何一個象限,第一象限為x,y>0的區域,第二象限為x<0,y>0的區域,第三象限為x,y<0的區域,第四象限為x>0,y<0的區域。
輸入描述:
每個測試點僅包含一組輸入數據。
僅一行空格隔開的三個整數a,b,c(-100 \leq a,b,c \leq 100)a,b,c(?100≤a,b,c≤100)
其中a和b不會同時等于0
輸出描述:
一行,按照順序輸出經過的象限。
如果直線不經過任何象限,請輸出"non"。
示例1
輸入
1 2 3
輸出
2 3 4
思路:
簡單的數學題,沒啥好說的…
代碼:
#include <bits/stdc++.h> using namespace std; int main() {int a, b, c;cin >> a >> b >> c;if (a == 0) {if (c == 0)cout << "non" << endl;else {if (b * c > 0)cout << "3 4" << endl;else cout << "1 2" << endl;}}else if (b == 0) {if (c == 0)cout << "non" << endl;else {if (a * c > 0)cout << "2 3" << endl;else cout << "1 4" << endl;}}else {if (c == 0)if (a * b > 0)cout << "2 4" << endl;else cout << "1 3" << endl;else {if (a * b < 0) {if (b * c < 0)cout << "1 2 3" << endl;else cout << "1 3 4" << endl;}else {if (b * c < 0)cout << "1 2 4" << endl;else cout << "2 3 4" << endl;}}}return 0; }D.財富密碼
鏈接:https://ac.nowcoder.com/acm/contest/5945/D
來源:牛客網
題目描述
“我們廈大的ACM實在是太厲害了”
在我校無數的菜雞中,這句話打開了財富之門,因此被稱為財富密碼。
事實上,關于密碼學的研究里面有很多涉及到數論的知識,以下就是一道例題。
求有多少整數n(1 \leq n \leq x)n(1≤n≤x)滿足na^{n} \equiv b (mod \ p)na
n≡b(mod p),其中p是一個質數。
看到這里你可能認為我會解釋上述符號的意思,然而如果你看不懂上面的式子,那么我不建議你嘗試這道題目,所以這里沒有解釋。
輸入描述:
每個測試點僅包含一組輸入數據。
第一行,四個以空格隔開的正整數,分別表示a,b,p,x(2 \leq p \leq 10^6,1 \leq x \leq 10^{12},1 \leq a,b < p)a,b,p,x(2≤p≤10 6,1≤x≤10 12,1≤a,b<p)
輸出描述:
一個正整數,符合條件的n的個數。
示例1
輸入
2 3 5 8
輸出
2
思路:
需要一些簡單的數論知識:
費馬小定理:若 p為質數,而整數a不是 p的倍數,則 。
逆元:定義整數 a 在模 p 意義下的逆元為 x,則 ,
可記作 。
在 1條件下,有 。
因為 。
然后到這道題:
首先由 費馬小定理 可以得到 ,
因此可以設 。
然后推式子:
由于 ,范圍為 。故可以枚舉 t,通過上式計算 k。
需要用到快速冪,一次計算的復雜度在 log級別。整體估計復雜度是可以通過的。
在已知 t 的情況下,我們根據這個式子可以求出一個 ,
顯然任意 ,
即 ,
都有: 時 。
有限制條件: 。
對于一個確定的 M>=0,我們要求的就是有多少個整數 ,使得 n在范圍之內。
然后在范圍內的計入答案就可以了。
代碼:
E.蕪湖起飛
鏈接:https://ac.nowcoder.com/acm/contest/5945/E
來源:牛客網
題目描述
安徽蕪湖有n個機場,一共有m條線路在空管部門報備。
每條線路單向連接兩個機場,并且需要的通行時間每天都可能不一樣。
具體來說,設目前是第x天,那么第i條線路所需要的通行時間為k_ix+b_ik
i x+b i。
一年一共有H天,也就是說,x取[0,H]中的整數。
現在大司馬想從1號機場在一天內換乘任意多次航班前往n號機場,他總是選擇用時最短的方式,現在他想知道哪一天需要花最長的時間。
輸入描述:
每個測試點僅包含一組輸入數據。
第一行三個整數n,m,H(1 \leq n,m \leq 114514,1 \leq H \leq 10^9)n,m,H(1≤n,m≤114514,1≤H≤10 9),表示機場的數量,線路的數量和x的取值范圍。
接下來m行,每行四個整數u_i,v_i,k_i,b_i(1 \leq u_i,v_i \leq n,-10^9 \leq k_i,b_i \leq 10^9)u i,v i,k i ,b i(1≤u i ,v i ≤n,?10 9 ≤k i,b i≤10 9),表示一條線路從u_iu i機場單向前往v_iv i機場,并且第x天需要k_ix+b_ik ix+b i的時間來通行。
同一對機場之間可能有多條航線,一條航線的起點和終點可能相同。
保證在[0,H]中的任意一天,每條航線的長度非負且不超過10^910
9,且從1號機場可以到達n號機場。
輸出描述:
輸出一行一個整數,表示最長的用時。
示例1
輸入
4 6 2
1 2 -2 6
1 3 3 3
1 4 -1 4
3 2 1 5
3 4 -3 7
2 4 0 5
輸出
4
思路:
這個題目的路徑是變化的,因為bi和ki是確定的,所以路徑長度隨著天數的變化而變化。題目要求的是0-h天里花費時間最長的那一天。因為路徑變化并沒有規律。考慮到bi和ai都是固定的,最短的情況應該就是第0天或者第h天。那么答案應該在0-h中間,0-h的最短路可以看作是一個向上凸的二次函數曲線,可以用三分的方法求出最高點。三分設左邊界為l,右邊界為r,ml=(l+r)>>1,mr=(r+ml)>>1;如果ml天的最短路大于mr天的最短路,那么答案可能的區間可以縮小到[l,mr],反之區間縮小為[ml,r]。mr-ml<10后,直接暴力把答案精確的求出來就行了
代碼:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 214514; const int inf = 0x3f3f3f3f; struct line{int v;ll k, b;};vector<line> g[N]; ll dist[N]; int n, m, h, vis[N];struct node{int v;ll cost;bool operator <(const node n)const{return cost > n.cost;} }; priority_queue<node>q;ll dij(ll x){for(int i = 1; i <= n; i++){dist[i] = 1e18;vis[i] = 0;}q.push({1, 0});dist[1] = 0;while(q.size()){node cd = q.top();q.pop();if(vis[cd.v])continue;vis[cd.v] = 1;for(line it: g[cd.v]){int to = it.v;ll cost = it.k * x + it.b;if(dist[to] > dist[cd.v] + cost){dist[to] = dist[cd.v] + cost;q.push({to, dist[to]});}}}return dist[n]; }ll work(int l, int r){if(r - l <= 10){ll ans = 0;for(int i = l; i <= r; i++){ans = max(ans, dij(1ll * i));}return ans;}int midl = (l + r) / 2;int midr = (midl + r) / 2;ll maxl = dij(1ll * midl);ll maxr = dij(1ll * midr);if(maxl >= maxr){return work(l, midr);}else{return work(midl, r);}}int main(){scanf("%d %d %d", &n, &m, &h);for(int i = 1; i <= m; i++){int u, v;ll k, b;scanf("%d %d %lld %lld", &u, &v, &k, &b);g[u].push_back({v, k, b});}printf("%lld\n", work(0, h));}F.這題多撈啊
鏈接:https://ac.nowcoder.com/acm/contest/5945/F
來源:牛客網
題目描述
給定一個正整數n,請求出所有滿足如下兩個條件的正整數集合x[1],x[2]…x[n]:
請按照集合中元素升序排序后字典序從小到大的順序輸出答案,若不存在這樣的集合請不要輸出任何字符。
輸入描述:
每個測試點僅包含一組測試數據。
第一行一個正整數n(1 \leq n \leq 1000)n(1≤n≤1000)。
輸出描述:
多行,每行代表一個可能的答案序列。
同一個序列內所有數從小到大排序,相鄰兩個數之間用一個空格隔開,行首尾不要添加多余空格。
示例1
輸入
3
輸出
1 1 4
思路:
我們先拿一些具體的例子試一試吧,看能不能找到突破口。
n == 1 :[2]
n == 2 :[1,3]
n == 3 :[1,1,4] 、[2,2,2]
n == 4 :[1,1,1,5]
n == 5 :[1,1,1,1,6] 、 [2,2,2,2,2]
。。。。。。。。。
我們似乎得到了什么規律了
首先對任意n,[1,1,1,1,1,1,…,n+1]一定是正確的(n-1個1,1個n+1)
而當n為奇數時[2,2,2,2,2,2,2…]也是正確的(n個2)
n==1時兩者重合了
這兩點都不難理解,重要的是接下來的一個歸納:
除了這兩種其他的任何集合都會有和為n的子集,不滿足情況!!!!!!!!!!!
下面我們來證明這個歸納!!
我們從這開始[1,1,1,1,1,1,…,n+1] 這是我們目前的序列
我們有n-1個1,1個n+1
我們從n+1向前扔k個1, n>=k>=1
這k個1一共落在了b個位置上, k>=b>=1
那么我們現在還擁有:
A、n-1-b 個 1
B、一個 n+1-k
C、b個總和為k+b,單個最小為2的數
我們要證明這些數一定能湊成n
首先我們有了n-1-b個1了那么這意味著什么?
意味著如果我們用B和C中的元素湊成
[b+1,b+2,b+3,b+4,…,n-1,n]
中的任意一個數值游戲結束!!!!!
而有趣的是,我們A,B,C中所有元素的數值總和為2n
那么B和C的元素的總和為2n - (n-1-b)
為n+1+b !!!(看上面的區間)
正好為:(b+1) + n、(b+2) + (n-1)、(b+3) + (n-2) 、(b+4) + (n-3) 。。。。。。。
而B和C中至少也有兩個元素,那么只要區間[b+1,b+2,b+3,b+4,。。。。n-1,n]
保持對稱性的情況下,一定能找到n即一定不成立!!!!!!
那么什么時候不保持對稱性呢?b+1 == n即b == n-1
也就是說,[1,1,1,1,1,…,n+1]最后一位向前n-1位都發了一個1
大家都變為了[2,2,2,2,2,2,2,2,2,2,…]
n位奇數時true,為偶數時false
證明完畢!!!!!!!!!!
證明并不嚴謹,可能會有漏洞,,,,如果發現希望指出,謝謝
代碼:
#include<bits/stdc++.h> using namespace std; int main() {int n;scanf("%d",&n);if(n==1){printf("2");return 0;}if(n&1){for(int i=1;i<=n-1;i++)printf("1 ");printf("%d\n",n+1);for(int i=1;i<=n;i++)printf("2 ");}else{for(int i=1;i<=n-1;i++)printf("1 ");printf("%d\n",n+1);}return 0; }G.正方形打野
鏈接:https://ac.nowcoder.com/acm/contest/5945/G
來源:牛客網
題目描述
大司馬的重要理論成果之一即所謂正方形打野,本題恰好與正方形相關。
大司馬的家的地板可以看成有n \times mn×m個格子的矩形。現在他需要用一些顏色的瓷磚來鋪滿這個房間,每種顏色的瓷磚擺放數量不受限制,但不能在同一個格子上覆蓋多塊瓷磚,更不能有空格子。
所有的瓷磚都是正方形的,然而這些瓷磚的邊長卻不一定相等,如:1 \times 11×1的瓷磚可以覆蓋一個格子,2 \times 22×2的瓷磚可以覆蓋4個格子。每一種不同的瓷磚的顏色分別為大寫字母A,B,C,D,E等以此類推,本題數據保證所需顏色不會超過26種。
大司馬是一個有強迫癥的人,他不能忍受地板上出現一個非正方形的色塊,即所有同色連通塊必須為正方形,這里的連通指上下左右四連通。
當大司馬的房子為4 \times 34×3時那么他的地板可以覆蓋成這樣:
AAA
AAA
AAA
BCB
不能覆蓋成這樣:
AAA
AAA
AAA
ACB
因為A對應的同色連通塊不是正方形,多了一塊角。
大司馬希望按照從上到下,從左到右的順序他房子地板顏色的字典序最小。
即將第一行,第二行……第n行從左到右對應的字母序列串成一個字符串,其字典序最小。
對于給定的n,m,請你輸出對應的方案。
輸入描述:
每個測試點僅包含一組輸入數據。
第一行兩個空格隔開的正整數n,m(n,m<=100)
輸出描述:
n行,每行一個長度為m的字符串,表示最終的擺放方案。
示例1
輸入
復制
13 15
輸出
復制
AAAAAAAAAAAAABA
AAAAAAAAAAAAACB
AAAAAAAAAAAAABA
AAAAAAAAAAAAACB
AAAAAAAAAAAAABA
AAAAAAAAAAAAACB
AAAAAAAAAAAAABA
AAAAAAAAAAAAACB
AAAAAAAAAAAAABA
AAAAAAAAAAAAACB
AAAAAAAAAAAAABA
AAAAAAAAAAAAACB
AAAAAAAAAAAAABA
思路:
我們仔細看這道題的要求:
1.保證所有連通塊是正方形
2.盡量讓這個地板從上到小,從左到右最小
那么我們想想,首相對于第一個格子我們首先肯定會鋪A之后向右看盡量鋪設A直到這一行鋪滿
或者說是列數不足以讓我們維持正方形了。
這里我們只要貪心的考慮讓右邊的格子盡量小就可以了,無需考慮下方。
那關鍵是接下來倘若行數沒有鋪完列數鋪完的情況下怎么考慮?
例:
AAAABA
AAAACB
AAAABA
AAAACB
我們是怎樣得出右邊的
BA
CB
BA
CB
的呢?
我們在上面的分析中有一句話:
這里我們只要貪心的考慮讓右邊的格子盡量小就可以了
也就是說我們只用考慮右方。
假設我們現在開始鋪設瓷磚B,我們判斷下一個即第一行最后一列該鋪設什么
我們有兩種選擇:
1.鋪設瓷磚B同時形成正方形(這里要保證不超過列數與行數)
2.鋪設其他瓷磚,瓷磚B的正方形到頭,新的時***啟。(這里的其他瓷磚是可鋪設的)
那我們的問題主要是接下來鋪設的時刻如何正確選擇操作1,2
我們會在兩種情況下使用操作2
(1):鋪設B無法形成正方形
(2):在可鋪設的瓷磚中有比B要小的瓷磚
滿足這兩個條件的任意一個,我們就不得不選擇操作2而非操作1
其實上述的兩種情況我們可以歸為一種:在可鋪設的瓷磚中最小的瓷磚不是B
那么我們就會采取操作2
如此我們從上到下,從左到右的遍歷矩陣,正方形的填充矩陣。
代碼:
#include<iostream>; #include<algorithm>; #include<vector>; using namespace std; int n, m; char ans[110][110]; int dir[4][2] = { 1,0,-1,0,0,1,0,-1 };char get(int x0,int y0) {vector<char> tmp;if (ans[x0][y0])return ans[x0][y0];for (int i = 0;i < 4;i++) {int x = x0 + dir[i][0];int y = y0 + dir[i][1];if (x > n || x <= 0 || y > m || y <= 0)continue;if (ans[x][y] != 0)tmp.push_back(ans[x][y]);}for (char ch = 'A';ch <= 'Z';ch++) {bool help = true;for (char cch : tmp)if (cch == ch) {help = false;break;}if (help)return ch;} }int main() {ios::sync_with_stdio(0);cin >> n >> m;for (int i = 1;i <= n;i++) {for (int j = 1;j <= m;j++) {if (ans[i][j])continue;char mychar = get(i, j);int len = 0;while (i + len <= n && j + len <= m && get(i, j + len) == mychar)len++;for (int k = 0;k < len;k++)for (int w = 0;w < len;w++)ans[i + k][j + w] = mychar;}}for (int i = 1;i <= n;i++) {for (int j = 1;j <= m;j++)cout << ans[i][j];cout << endl;} }H.時間管理
鏈接:https://ac.nowcoder.com/acm/contest/5945/H
來源:牛客網
題目描述
大司馬每天日程太多,需要一個高效的數據結構進行時間管理。經過研究,他認為這個問題可以被歸結如下:
給定一個長度為n的序列,第i個元素為a_ia
i
?
,請支持如下兩種操作:
1\ l\ r\ x(1 \leq l \leq r \leq n,1 \leq x \leq 10^9)1 l r x(1≤l≤r≤n,1≤x≤10
9
),表示將a_l \sim a_ra
l
?
~a
r
?
的值都與x取最大公約數,即對于l \leq i \leq rl≤i≤r,將a_ia
i
?
替換為gcd(a_i,x)gcd(a
i
?
,x),兩個數的最大公約數是能夠同時整除兩個數的最大數。
2\ l\ r(1 \leq l \leq r \leq n)2 l r(1≤l≤r≤n),詢問此時a_l \sim a_ra
l
?
~a
r
?
的和。
請注意,操作有時間順序,2類操作輸出的是進行詢問時對應區間的和。
輸入描述:
每個測試點僅包含一組輸入數據。
第一行兩個整數n,m(1 \leq n,m \leq 114514)n,m(1≤n,m≤114514),表示序列長度和操作個數。
第二行n個整數,第i個整數表示a_ia
i
?
的初始值(1 \leq a_i \leq 10^9)(1≤a
i
?
≤10
9
)。
接下來m行,每行為題目描述提到的的兩種格式之一,表示一次操作,操作按照時間順序給出。
輸出描述:
按照輸入順序,對于每個2類操作,輸出一行一個整數表示對應的和。
示例1
輸入
6 4
9 9 6 2 5 1
1 1 3 6
2 2 5
1 2 5 4
2 1 6
輸出
16
10
思路:
這道題跟區間開方思路類似。
每次對一個區間進行gcd的話一般會有大部分會變成1,可以用一些小技巧來保證復雜度不會太差,用一個tag變量去標記一下這個區間是不是全都相等,再用一個sam變量去標記區間全相等的時候的元素大小,這樣修改的時候對于區間元素都相等的直接對sam進行gcd即可。最后用一個線段樹維護標記。
代碼:
#include<bits/stdc++.h> #define ls cur<<1 #define rs cur<<1|1 using namespace std;typedef long long ll; const int maxn=2e5+10;ll sum[maxn<<2],a[maxn<<2],ma[maxn<<2],gd[maxn<<2],tag[maxn<<2],sam[maxn<<2];void pushup( int cur ) {sum[cur]=sum[ls]+sum[rs]; tag[cur]=tag[ls] & tag[rs];if( tag[cur] ) {if( sam[ls]==sam[rs] ) sam[cur]=sam[ls]; else tag[cur]=0;} }void pushdown( int cur ,int l,int r ) {if( tag[cur] ){int mid=l+r>>1;sam[ls]=sam[rs]=sam[cur];sum[ls]=(mid-l+1)*sam[ls];sum[rs]=(r-mid)*sam[rs];}}void build( int cur,int l,int r ) {if( l==r ){sum[cur]=a[l];tag[cur]=1;sam[cur]=a[l];return;}tag[cur]=0;int mid=l+r>>1;build(ls,l,mid);build(rs,mid+1,r);pushup(cur); }void update( int cur,int l,int r,int L,int R,ll p ) {if( L<=l && r<=R ){if( tag[cur] ){sam[cur]=__gcd(sam[cur],p);sum[cur]=sam[cur]*(r-l+1);return;}}pushdown(cur,l,r);if( l==r ){sam[cur]=sum[cur]=__gcd(a[l],p);return;}int mid=l+r>>1;if( mid>=L ) update(ls,l,mid,L,R,p);if( mid<R ) update(rs,mid+1,r,L,R,p);pushup(cur); }ll get_sum( int cur,int l,int r,int L,int R ) {if( L<=l && r<=R ) return sum[cur];int mid=l+r>>1;ll ans=0;pushdown(cur,l,r);if( L<=mid ) ans+=get_sum(ls,l,mid,L,R);if( R>mid ) ans+=get_sum(rs,mid+1,r,L,R);return ans; }如有錯誤,歡迎指正~
總結
以上是生活随笔為你收集整理的厦门大学“网宿杯“17届程序设计竞赛决赛(同步赛) #题解 #题目都超有趣呀的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 以太网交换机和普通交换机主要的8大区别介
- 下一篇: flutter项目:启动名称生成器(代码