2017廣東工業大學程式設計競賽決賽 題解&原始碼(A,數學解方程,B,貪心博弈,C,遞迴,D,水,E,貪心,面試題,F,貪心,列舉,LCA,G,dp,記憶化搜尋,H,思維題)...

weixin_33896726發表於2017-03-26

心得:

這比賽真的是不要不要的,pending了一下午,也不知道對錯,直接做過去就是了,也沒有管太多!

Problem A: 兩隻老虎

Description

 

來,我們先來放鬆下,聽聽兒歌,一起“唱”。

兩隻老虎兩隻老虎,跑得快跑得快。

一隻沒有耳朵,一隻沒有尾巴。

真奇怪,真奇怪。

Tmk也覺得很奇怪,因為在他面前突然出現了一群這樣的老虎,有的沒耳朵,有的沒尾巴,不過也有正常的。

現在Tmk告訴你這群老虎的耳朵個數,尾巴條數,以及老虎的腿的數目,問你有多少隻是正常的。

其中只有三種老虎:

第一種(正常的):有2個耳朵、1條尾巴、4條腿

第二種(沒耳朵):有0個耳朵、1條尾巴、4條腿

第三種(沒尾巴):有2個耳朵、0條尾巴、4條腿

Input

 

 

 

第一行一個整數T表示有多少組樣例。

接下來每一行一個樣例:

包含三個整數abc表示總共有a個耳朵,b條尾巴,c(<=4000)條腿,資料保證有解。

Output

 

對於每組樣例輸出一行,表示有多少隻正常的老虎。

Sample Input

1
12 7 40

Sample Output

3

HINT

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=0

分析:解方程咯,設以上三種情況分別為x,y,z,則有下列三個方程組:

2*x+2*z=a/2;

x+y=b;

4*x+4*y+4*z=c;

解得x=a/2+b-c/4;即為答案!

下面給出AC程式碼:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int T;
 6     int a,b,c;
 7     while(cin>>T)
 8     {
 9         while(T--)
10         {
11             cin>>a>>b>>c;
12             c/=4;
13             a/=2;
14             int x=a+b-c;
15             printf("%d\n",x);
16         }
17     }
18     return 0;
19 }

Problem B: 佔點遊戲

Description

 

 

 

 

 

眾所周知的是,TMK特別容易遲到,終於在TMK某次又遲到了之後,Maple怒了,Maple大喊一聲:“我要跟你決一死戰!”然後Maple就跟TMK玩起了一個關於佔點的遊戲。

Maple在一個無限展開的只有整數點的二維平面上找到兩個點,由TMKMaple分別操控這兩個點,兩人輪流操作,每一次操作中TMKMaple可以把他的點移動一格到上、下、左、右四個方向,當TMK操作時,移動到的這個點會被染成紅色,而當Maple操作時,移動到的這個點會被染成藍色,需要注意的是,兩個起始時的兩個點也都會被染上相應的顏色,而當任一人走到已經染了不同顏色的點,這個顏色會被覆蓋掉,當兩個點覆蓋在一起時,這個點會被後來的點染色。當遊戲結束時染著自己顏色的點就代表被自己佔領了。

TMK一下就明白了,這個遊戲的目標是讓自己佔領的點比對方佔領的點多,而且要讓差值最大。

為了公平一些,Maple決定讓TMK來選擇先手或後手和讓TMK來選擇點,相應的Maple就會選擇另一個點。

現在給出遊戲的總輪數NMaple選擇的兩個點的座標(x1y1),(x2y2),要TMK來選擇先後手和起始點,假設Maple一定按最優策略來走,問TMK能不能選擇先後手和起始點使得自己佔領的點比Maple佔領的多,如果能,那麼同時要求出佔領的點數的最大差值。

 

 

 

Input

 

 

 

第一行一個T,代表接下來有T組資料(1<=T<=2000)

每組資料有五個整數N,x1,y1,x2,y2,代表了操作的總輪數N以及選擇的兩個起始點(x1,y1),(x2,y2),其中1<=N<=10^8,-10^8<=x1,y1,x2,y2<=10^8,資料保證兩個點不相同。

 

Output

對於每一組資料,如果TMK佔領的點不能比Maple佔領的多,那麼輸出-1,否則輸出兩個佔領點數的最大差值。

Sample Input

4
1 0 0 1 0
2 0 0 1 0
1 0 0 2 0
2 0 0 2 0

Sample Output

2
-1
1
-1

HINT

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=1

分析:令 d = abs(x1-x2)+abs(y1-y2)
首先判斷(n+1)/2 >= d,先手可不可以從一個點走到另一個點 :
如果不可以,則先手可以多得 n&1 分(因為劣勢者可以選擇逃離)
如果可以,考慮 d 的奇偶性:
如果 d 為奇數(先手可以先踩到後手覆蓋過的點):
如果 n 為奇數,先手可以多得 2 分,否則平。
否則(d 為偶數):
如果 n 為奇數,先手可以多得 1 分,否則後手可以多得 1 分。

下面給出AC程式碼:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int n,x1,x2,y1,y2;
 6     int T;
 7     while(scanf("%d",&T)!=EOF)
 8     {
 9       while(T--)
10       {
11         scanf("%d%d%d%d%d",&n,&x1,&y1,&x2,&y2);
12         int d=abs(x1-x2)+abs(y1-y2);
13         int ans=-1;
14         if((n+1)/2>=d)
15         {
16             if(d%2!=0)
17             {
18                 if(n%2!=0)
19                 {
20                     ans=2;
21                 }
22             }
23             else
24             {
25                 ans=1;
26             }
27         }
28         else if(n%2!=0)
29         {
30             ans=1;
31         }
32         printf("%d\n",ans);
33       }
34     }
35     return 0;
36 }

 

Problem C: 爬樓梯

Description

 

小時候,我只能一階一階得爬樓梯,

後來,我除了能一次爬一階,還可以一次爬兩階,

到現在,我最多一次可以爬三階。

那麼現在問題來了,我想爬上n層樓,相鄰樓層之間有一段樓梯,雖然我一次可以爬1個臺階、2個臺階和3個臺階,但是我在ii+1層之間的樓梯上時,我不能跨越到i+1i+2層之間的樓梯。現在有個n層的樓,知道每一段樓梯的階數,我想知道,如果我只會往上走,並且忽略其他不在樓梯上的其他移動,共有多少種方案可以到達第n層。

Input

 

第一行一個整數T0<T<=50)表示有多少組樣例。

對於每一組樣例:

第一行一個n1<n<=50)表示有多少層樓。

接下來一行,包括n-1個整數xi0<xi<=20),由下到上依次表示每段樓梯的長度。

 

Output

 

對於每組資料,輸出一行表示共有多少種方案。由於答案較大,所以輸出答案請對10007取模。

 

Sample Input

2
2
3
4
4 5 6

Sample Output

4
2184

HINT

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=2

分析:遞迴求解,

C[0]=C[1]=1;C[2]=2;

C[i]=C[i-1]+C[i-2]+C[i-3];

此題給出的範圍剛好合適,大了應該會TL

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int b[55];
 4 int gcd(int n)
 5 {
 6     b[0]=1;
 7     b[1]=1;
 8     b[2]=2;
 9     for(int i=3;i<=n;i++)
10         b[i]=b[i-1]+b[i-2]+b[i-3];
11     return b[n];
12 }
13 int main()
14 {
15     int T;
16     int n,t;
17     int a[55];
18     while(cin>>T)
19     {
20         while(T--)
21         {
22             cin>>n;
23             int s=1;
24             for(int i=0;i<n-1;i++)
25                 cin>>a[i];
26             for(int i=0;i<n-1;i++)
27             {
28                 int t=gcd(a[i]);
29                 s*=t;
30                 s%=10007;
31             }
32             printf("%d\n",s);
33         }
34     }
35     return 0;
36 }

Problem D: 只有通過毀滅才能揭示真理

Description

 

“只有通過毀滅才能揭示真理。” —— 虛空之眼

維克茲是一個有觸手的虛空來客,他帶著非凡的意圖探索著符文之地:吸收掉所有知識。憑藉著他不斷地注視,維克茲可以發射瓦解光線來滅除並分析他途中的一切東西,併為他供給數量龐大的資訊。沒人知道他為什麼需要如此多的材料,儘管有人推測他設法瞭解符文之地,是為了加速它的毀滅。

另外,維克茲本身也是一個極其強大的魔法師,他的技能會對命中的敵人施加有機體解構效果。如果累積到3層效果,敵人就會受到爆發性的真實傷害。

現在,維克茲正準備施展他的絕招 —— 生命形態瓦解射線,來對付被永久眩暈且沒有攜帶任何魔抗裝備的約德爾人。另外,他的絕招每10秒就可以對敵人累積一層有機體解構效果。

維克茲希望找到能夠跟他一起遨遊大陸的夥伴,所以他準備考考你,如果已知生命形態瓦解射線持續的時間和每一秒的傷害,以及有機體解構效果每累積到3層所爆發的傷害(傷害爆發後層數歸零),你是否能算出約德爾人受到的總傷害是多少呢?

請注意,如果你回答不出來,維克茲絕對很樂意將你一起分解掉。

 

Input

 

輸入包括T組資料,每組資料包括生命形態瓦解射線的持續時間A,每一秒的傷害B,以及有機體解構效果每累積到3層所爆發的傷害C

T <= 10000, 0 <= A, B, C <= 10000, 所有資料皆為整數)

Output

 

輸出一個數代表約德爾人受到的總傷害。

 

Sample Input

2
10 10 10
30 10 10

Sample Output

100
310

HINT

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=3
分析:每三十次進一,類似於30進位制,每十次傷害加c
下面給出AC程式碼:
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int T;
 6     int a,b,c;
 7     while(cin>>T)
 8     {
 9         while(T--)
10         {
11             cin>>a>>b>>c;
12             int ans=a*b;
13             int t=a/30;
14             while(t)
15             {
16                 t--;
17                 ans+=c;
18             }
19         cout<<ans<<endl;
20         }
21     }
22     return 0;
23 }

Problem E: 倒水(Water)

Description

 

一天,CC買了N個容量可以認為是無限大的瓶子,開始時每個瓶子裡有1升水。接著~~CC發現瓶子實在太多了,於是他決定保留不超過K個瓶子。每次他選擇兩個當前含水量相同的瓶子,把一個瓶子的水全部倒進另一個裡,然後把空瓶丟棄。(不能丟棄有水的瓶子)

顯然在某些情況下CC無法達到目標,比如N=3,K=1。此時CC會重新買一些新的瓶子(新瓶子容量無限,開始時有1升水),以到達目標。

現在CC想知道,最少需要買多少新瓶子才能達到目標呢?

Input

 

第一行一個整數T,表示有T組資料。

接著T每行兩個正整數, N,K(1<=N<=10^9K<=1000)

 

Output

 

一個非負整數,表示最少需要買多少新瓶子。

Sample Input

3
3 1
13 2
1000000 5

Sample Output

1
3
15808

HINT

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=4
分析:此題貪心求解,好像是一道面試題,其實就是考驗思維能力,

因為每個瓶子的水都一定是2的倍數,所以最少的瓶子數就是總數的二進位制表示中1的個數。每次加上最低位的1直到滿足要求,來一波位運算。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define LL long long
 4 int cnt(LL x)
 5 {
 6     int ret=0;
 7     while(x)
 8     {
 9         if(x&1) ret++;
10         x>>=1;
11     }
12     return ret;
13 }
14 int main()
15 {
16     int i,j,k,n;
17     LL m,ans,x,y,z;
18     int T;
19     while(cin>>T)
20     {
21         while(T--)
22         {
23            scanf("%lld%d",&m,&n);
24            ans=0;
25            while(cnt(m)>n)
26            {
27              for (x=1;;x<<=1)
28              if(m&x)
29                 break;
30               ans+=x;
31               m+=x;
32            }
33            printf("%lld\n",ans);
34         }
35     }
36     return 0;
37 }

下面給出官方的題解:

對於 n 瓶一升的水,把他們合併後,最少需要的瓶子數為 n 的二進位制中 1 的個數。假
設 n 的二進位制中 1 的個數大於 k,那麼我們要找到 1 個大於 n 的數,且二進位制中 1 的個數等
於 k 的最小的數 m,那麼答案為 m-n。
假設 n 二進位制中,最右邊的 1 在第 i 位(這裡的第幾位是從右往左數的,最右邊為第 0
位),那麼假設你加上一個小於 2^i 的數,結果二進位制中 1 的個數只會增加,如果加上一個
2^i,則結果二進位制中 1 的個數必定不會增加。所以只需要一直加上一個 2^i(這裡的 i 表示
的是當前水的總體積的二進位制中最右邊的 1 所在的位置)直到結果中 1 的個數等於 k 即可。

 1 #include <iostream>
 2 using namespace std;
 3 int cal(int n)
 4 {
 5     int ans = 0;
 6     while(n)
 7     {
 8         ans ++;
 9         n &= n-1;
10     }
11     return ans;
12 }
13 int lowbit(int n)
14 {
15     return n&-n;
16 }
17 int main()
18 {
19     int T;
20     scanf("%d",&T);
21     while(T--)
22     {
23         int n, k;
24         scanf("%d%d", &n, &k);
25         int ans = 0;
26         while(cal(n) > k)
27         {
28             ans += lowbit(n);
29             n += lowbit(n);
30         }
31         printf("%d\n", ans);
32     }
33     return 0;
34 }

 

Problem F: tmk找三角

Description

 

有一棵樹,樹上有隻tmk。他在這棵樹上生活了很久,對他的構造瞭如指掌。所以他在樹上從來都是走最短路,不會繞路。他還還特別喜歡三角形,所以當他在樹上爬來爬去的時候總會在想,如果把剛才爬過的那幾根樹枝/樹幹鋸下來,能不能從中選三根出來拼成一個三角形呢?

 

Input

 

第一行輸入一個T,表示有多少組樣例。

對於每組資料:第一行包含一個整數 N,表示樹上節點的個數(從 1 N 標號)。

接下來的 N-1 行包含三個整數 a, b, len,表示有一根長度為 len 的樹枝/樹幹在節點 a 和節點 b 之間。

接下來一行包含一個整數 M,表示詢問數。

接下來M行每行兩個整數 S, T,表示毛毛蟲從 S 爬行到了 T,詢問這段路程中的樹枝/樹幹是否能拼成三角形。

 

Output

 

對於每組資料,每個詢問輸出一行,包含"Yes"“No”,表示是否可以拼成三角形。

 

Sample Input

2
5
1 2 5
1 3 20
2 4 30
4 5 15
2
3 4
3 5
5
1 4 32
2 3 100
3 5 45
4 5 60
2
1 4
1 3

Sample Output

No
Yes
No
Yes

HINT

對於20%資料 1 ≤ N, M ≤ 1000

對於所有資料1 ≤ N ≤ 100000, 1 ≤ M ≤ 100000, 1 ≤ len ≤ 1000000000

 

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=5

 

分析:這道題如果直接按照題意去寫,那麼可以利用廣度優先搜尋得到最短路徑(因為這是一顆樹,而不是圖,所以不必使用最短路演算法),然後判斷路徑上的邊是否能組成一個三角形(先對路徑排序,然後用兩邊之和大於第三邊進行判斷)。不過搜尋的時間複雜度是 O(N),判斷三角形的時間複雜度為 O(llgl)(其中 l 是最短路徑的長度),小資料沒問題,但大資料肯定會掛。

 

LCA可是可以寫,不過估計會TL,目前還沒想到什麼好辦法!
先給出一種LCA寫法吧,我也沒敢提交,估計會TL,課後補了下重掛賽,AC了,竟然不會超時,只能說資料太水!
  1 #include <stdio.h>
  2 #include <cmath>
  3 #include <algorithm>
  4 #include <list>
  5 #include <string.h>
  6 using namespace std;
  7 // 樹的節點
  8 struct Node {
  9 int next, len;
 10 Node (int n, int l):next(n), len(l) {}
 11 };
 12 int pow2[20];
 13 list<Node> nodes[100010];
 14 bool visit[100010];
 15 int ns[200010];
 16 int nIdx;
 17 int length[100010];
 18 int parent[100010];
 19 int depth[200010];
 20 int first[100010];
 21 int mmin[20][200010];
 22 int edges[100010];
 23 // DFS 對樹進行預處理
 24 void dfs(int u, int dep)
 25 {
 26 ns[++nIdx] = u; depth[nIdx] = dep;
 27 visit[u] = true;
 28 if (first[u] == -1) first[u] = nIdx;
 29 list<Node>::iterator it = nodes[u].begin(), end = nodes[u].end();
 30 for (;it != end; it++)
 31 {
 32 int v = it->next;
 33 if(!visit[v])
 34 {
 35 length[v] = it->len;
 36 parent[v] = u;
 37 dfs(v, dep + 1);
 38 ns[++nIdx] = u;
 39 depth[nIdx] = dep;
 40 }
 41 }
 42 }
 43 // 初始化 RMQ
 44 void init_rmq()
 45 {
 46 nIdx = 0;
 47 memset(visit, 0, sizeof(visit));
 48 memset(first, -1, sizeof(first));
 49     depth[0] = 0;
 50     length[1] = parent[1] = 0;
 51     dfs(1, 1);
 52     memset(mmin, 0, sizeof(mmin));
 53     for(int i = 1; i <= nIdx; i++) {
 54         mmin[0][i] = i;
 55     }
 56     int t1 = (int)(log((double)nIdx) / log(2.0));
 57     for(int i = 1; i <= t1; i++) {
 58         for(int j = 1; j + pow2[i] - 1 <= nIdx; j++) {
 59             int a = mmin[i-1][j], b = mmin[i-1][j+pow2[i-1]];
 60             if(depth[a] <= depth[b]) {
 61                 mmin[i][j] = a;
 62             } else {
 63                 mmin[i][j] = b;
 64             }
 65         }
 66     }
 67 }
 68 // RMQ 詢問
 69 int rmq(int u, int v)
 70 {
 71     int i = first[u], j = first[v];
 72      if(i > j) swap(i, j);
 73      int t1 = (int)(log((double)j - i + 1) / log(2.0));
 74      int a = mmin[t1][i], b = mmin[t1][j - pow2[t1] + 1];
 75      if(depth[a] <= depth[b]) {
 76          return ns[a];
 77      } else {
 78          return ns[b];
 79      }
 80  }
 81 
 82 int main() {
 83      for(int i = 0; i < 20; i++) {
 84           pow2[i] = 1 << i;
 85      }
 86      int T, n, m, a, b, len;
 87      scanf("%d ", &T);
 88      for (int caseIdx = 1;caseIdx <= T;caseIdx++) {
 89          scanf("%d", &n);
 90          for (int i = 0;i <= n;i++) {
 91              nodes[i].clear();
 92          }
 93          for (int i = 1;i < n;i++) {
 94              scanf("%d%d%d", &a, &b, &len);
 95              nodes[a].push_back(Node(b, len));
 96              nodes[b].push_back(Node(a, len));
 97          }
 98          init_rmq();
 99          scanf("%d", &m);
100          //printf("Case #%d: ", caseIdx);
101          for (int i = 0;i < m;i++) {
102              scanf("%d%d", &a, &b);
103              // 利用 RMQ 得到 LCA
104              int root = rmq(a, b);
105              bool success = false;
106              int l = 0;
107              while (a != root) {
108                  edges[l++] = length[a];
109                  a = parent[a];
110              }
111              while (b != root) {
112                  edges[l++] = length[b];
113                  b = parent[b];
114              }
115              if (l >= 3) {
116                  sort(edges, edges + l);
117                  for (int j = 2;j < l;j++) {
118                      if (edges[j - 2] + edges[j - 1] > edges[j]) {
119                          success = true;
120                          break;
121                      }
122                  }
123              }
124              if (success) {
125                  puts("Yes");
126              } else {
127                  puts("No");
128              }
129          }
130      }
131   return 0;
132 }

官方題解來了:

假設現在有 n 條線段,假設 n 條邊從小到達排序,如果這 n 條邊中沒有三條可以構成
三角形,那麼這 n 條邊必須滿足關係:A[i] >= A[i-2]+A[i-1],這裡的 A[i]表示第 i 條邊的大小。
假設 A[i]儘量取最小 A[i]=A[i-2]+A[i-1],且 A[1]=A[2]=1,是不是就是一個斐波那契,也就
是對於一個 n 條邊的集合,如果不存在三條邊能構成一個三角形,那麼最長的邊至少為 f[n],
表示斐波那契第 n 項。而題目中 A[i]<1e9,也就是隻要 n>50,就必定存在三條邊可以構成一
個三角形,所以我們只需要暴力加入兩點路徑上的邊(如果大於 50,直接 Yes),然後對這
些邊進行排序,列舉第 i 條邊為最長邊,貪心判斷 A[i]是否小於 A[i-1]+A[i-2]即可。

 1 #include <cstdio>
 2 #include <set>
 3 #include <cstring>
 4 #include <iostream>
 5 #include <vector>
 6 #include <algorithm>
 7 using namespace std;
 8 
 9 const int N = 1e5+10;
10 int n;
11 int tot, head[N], to[N<<1], nex[N<<1], len[N<<1]; 
12 int f[N], dis[N], dep[N];
13 
14 void init()
15 {
16     tot = 0;
17     for(int i = 0; i <= n; ++ i)
18     {
19         head[i] = -1;
20     }
21 }
22 
23 void addEdge(int x, int y, int l)
24 {
25     to[tot] = y;
26     len[tot] = l;
27     nex[tot] = head[x];
28     head[x] = tot++;
29 }
30 
31 void dfs(int u, int d)
32 {
33     dep[u] = d;
34     for(int i = head[u]; ~i; i = nex[i])
35     {
36         int v = to[i];
37         if(v == f[u]) continue;
38         dis[v] = len[i];
39         f[v] = u;
40         dfs(v, d+1);
41     }
42 }
43 
44 bool solve(int x, int y)
45 {
46     vector<int> vec;
47     while(vec.size() < 50 && x != y)
48     {
49         if(dep[x] < dep[y])
50         {
51             vec.push_back(dis[y]);
52             y = f[y];
53         }
54         else
55         {
56             vec.push_back(dis[x]);
57             x = f[x];
58         }
59     }
60     if(vec.size()>=50) return true;
61     sort(vec.begin(), vec.end());
62     for(int i = 0; i + 2 < vec.size(); ++ i)
63     {
64         if(vec[i] + vec[i+1] > vec[i+2]) return true;
65     }
66     return false;
67 }
68 
69 int main()
70 {
71     int T;
72     scanf("%d", &T);
73     while(T--)
74     {
75         scanf("%d", &n);
76         init();
77         for(int i = 1; i < n; ++ i)
78         {
79             int x, y, l;
80             scanf("%d%d%d", &x, &y, &l);
81             addEdge(x, y, l);
82             addEdge(y, x, l);
83         }
84         dfs(1, 0);
85         int m;
86         scanf("%d", &m);
87         for(int i = 0; i < m; ++ i)
88         {
89             int x, y;
90             scanf("%d%d", &x, &y);
91             puts(solve(x, y) ? "Yes" : "No");
92         }
93     }
94     return 0;
95 } 

 

Problem G: 等凹數字

Description

定義一種數字稱為等凹數字,即從高位到地位,每一位的數字先非遞增再非遞減,不能全部數字一樣,且該數是一個迴文數,即從左讀到右與從右讀到左是一樣的,僅形成一個等凹峰,如5432123455544334455是合法的等凹數字,543212346123321,111111不是等凹數字。現在問你[L,R]中有多少等凹數字呢?

 

Input

第一行一個整數T,表示資料的組數。

接下來T行每行倆個數字L和R,(1<=L<=R<=1e18)

Output

 輸出一個整數,代表[L,R]中有多少等凹數字

Sample Input

2
1 100
101 200

Sample Output

0
1

HINT

 

 小於等於2位的數字無凹峰

 

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=6

 

分析:dp[i][len][pre][up][down][ispa]代表當前第 i 位,長度為 len,上一位是什麼,前面是否遞
增過,前面是否遞減過,當前是否符合迴文串的性質,然後記憶化搜尋。

 

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <iostream>
 4 #include <algorithm>
 5 #include <vector>
 6 #include <queue>
 7 #include <time.h>
 8 #include <set>
 9 #include <map>
10 #include <string>
11 #include <math.h>
12 #include <stdlib.h>
13 using namespace std;
14 long long dp[20][20][10][2][2][2];
15 int num[20];
16 int s[20];
17 long long rec(int i,int pre,int up,int down,int flag,int q,int len,int ispa)
18 {
19     if(i<0)return up&&down&&ispa;
20     if(~dp[i][len][pre][up][down][ispa]&&!flag&&!q)return dp[i][len][pre][up][down][ispa];
21     long long res=0;
22     int o=s[i];
23     for(int j=0;j<10;j++)
24     {
25         num[i]=j;
26         if(j>o&&flag)break;
27         if(q)res+=rec(i-1,j,0,0,j<o?0:flag,q&&j==0,len-(q&&j==0),ispa);
28         else if(j==pre)
29         {
30             if(ispa&&i<len/2)
31             res+=rec(i-1,j,up,down,j<o?0:flag,q&&j==0,len,j==num[len-i-1]);
32             else res+=rec(i-1,j,up,down,j<o?0:flag,q&&j==0,len,ispa);
33         }
34         else if(j>pre)
35         {
36             if(!down)continue;
37             if(ispa&&i<len/2)
38             res+=rec(i-1,j,1,down,j<o?0:flag,q&&j==0,len,j==num[len-i-1]);
39             else res+=rec(i-1,j,1,down,j<o?0:flag,q&&j==0,len,ispa);
40         }
41         else if(j<pre)
42         {
43             if(up)continue;
44             if(ispa&&i<len/2)
45             res+=rec(i-1,j,up,1,j<o?0:flag,q&&j==0,len,j==num[len-i-1]);
46             else res+=rec(i-1,j,up,1,j<o?0:flag,q&&j==0,len,ispa);
47         }
48     }
49     if(!flag&&!q)dp[i][len][pre][up][down][ispa]=res;
50     return res;
51 }
52 long long cal(long long x)
53 {
54     int len=0;
55     while(x)
56     {
57         s[len++]=x%10;
58         x/=10;
59     }
60     return rec(len-1,0,0,0,1,1,len,1);
61 }
62 int main()
63 {
64     memset(dp,-1,sizeof(dp));
65     long long l,r;
66     int t;
67     scanf("%d",&t);
68     while(t--){
69     scanf("%lld%lld",&l,&r);
70     printf("%lld\n",cal(r)-cal(l-1));
71     }
72     return 0;
73 }

 

Problem H: tmk買禮物

 

Description

 

 

今天是校賽的日子,為了慶祝這麼喜慶的日子,TMK打算買些禮物給女票LSH慶祝一下。

TMK進入了雪梨超市,然後剛踏入的一瞬間,店主就對TMK說:“恭喜你成為了本店第2147483647位顧客,本店在搞一個活動,對本店第2147483647位顧客進行贈送活動。你先看看你有多少錢?”

TMK一摸口袋,發現只有n個硬幣,每個硬幣的價值為a[i]

然後店主繼續說:“現在你用你的錢湊一些數,如果你的錢能湊成[0,x]裡面所有的數,那麼你將會免費獲得該店價值x元的代金券,假設你有四個硬幣面值分別為1,2,4100,你就可以湊成[0,7]裡面所有的數,我們將會送你7元的代金券。現在就用你的硬幣來試試吧。Enjoy yourself!”

TMK努力湊錢的時候,店主想知道他要送多少代金券給TMK

 

 

Input

 

 

第一行一個整數T,表示資料組數。

對於每組資料,首先讀入一個整數nn<=100000),然後接下來的一行有n個整數,表示a[i]0<a[i]<=1e9

 

Output

 

 

對於每個資料,輸出一個整數x,表示店主要送x元的代金券給TMK

 

 

Sample Input

 

1
3
1 2 3

 

Sample Output

 

6

 

HINT

題目連結:http://gdutcode.sinaapp.com/problem.php?cid=1056&pid=7

 還是給下正解的思路吧,可能是因為資料太水就過了!

思路:先將所有的數排序,先特判一下第一個數是不是1,如果不是的話,那麼肯定不可能有x,否則就找到第一個a[i]使得
a[i]>a[i-1]+...a[0]+1,這個a[i]就是一定不能組成的
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int t;
 4 int n;
 5 long long a[100005];
 6 long long res;
 7 void init(){
 8     res=0;
 9 }
10 int main(){
11     // freopen("in.txt","r",stdin);
12     scanf("%d",&t);
13     while(t--){
14         init();
15         scanf("%d",&n);
16         for(int i=1;i<=n;i++){
17             scanf("%lld",&a[i]);
18         }
19         sort(a+1,a+n+1);
20         int f=0;
21         for(int i=1;i<=n;i++){
22             if(a[i]>res+1){
23                 f=1;
24                 printf("%lld\n",res);
25                 break;
26             }
27             res+=a[i];
28         }
29         if(f==0){
30             printf("%lld\n",res);
31         }
32     }
33     return 0;
34 }
另外一種解法:
首先,先對 a[i]從小到大排序,假設對於前 i 個硬幣,我們可以組合成 0~y:
①如果 a[i+1]>y+1,那麼從 i+1~n 中任意取硬幣,構成的和都>y+1,所以必定構造不出
y+1,於是答案等於 y。
②如果 a[i+1]<=y+1,那麼前 i+1 位可以組合成 0~y+a[i+1]。
所以只需要對硬幣從小到大排序,然後從第一個硬幣列舉到最後一個硬幣,或者中途有
某個數夠不出來即可得到答案。
要注意,輸出要用long long型,否則會溢位!
下面給出AC程式碼:
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int T;
 6     int n;
 7     int a[100005];
 8     while(cin>>T)
 9     {
10         while(T--)
11         {
12             cin>>n;
13             for(int i=0;i<n;i++)
14                 cin>>a[i];
15             sort(a,a+n);
16             long long ans=0;
17             for(int i=0;i<n&&ans+1>=a[i];i++)
18                 ans+=a[i];
19             printf("%lld\n",ans);
20         }
21     }
22     return 0;
23 }

 

 


 

 

 

相關文章