Atcoder 刷題


1973. Iroha's Obsession

題意:給你一個小於1e4的正整數n,和一個合法digit集合(保證至少有一個非0 digit合法)。稱一個數字合法,當且僅當它所包含digit都合法,讓你找出一個不小於n的最小合法數字。

觀察:因為至少有一個合法的非0digit,那么至少 11111, 22222, ...., 99999 當中有一個合法,所以,答案不會超過 99999。

方法:

1. 直接暴力枚舉ans,然后判斷ans是否可行即可。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 bool valid[10];
13 inline bool solve(int n)
14 {
15   while (n)
16     {
17       if (!valid[n%10])
18     return false;
19       n /= 10;
20     }
21   return true;
22 }
23 int main()
24 {
25   int n, k;
26   scanf("%d %d", &n, &k);
27   memset(valid, 1, sizeof(valid));
28   for (int i = 0; i < k; ++i)
29     {
30       int x;
31       scanf("%d", &x);
32       valid[x] = false;
33     }
34   while (!solve(n))
35     ++n;
36   printf("%d\n", n);
37 }
View Code

2. 其實可以從高位到低位找到第一個不合法的位置,更改這個位置之后,它后面的數位都變成了0,那么一定都會取最小的合法digit。所以只需要找出這個位置就好了。注意可能會有向前進位的情況,處理一下。如果預處理出nxt[i] = 不小於i的最小合法digit, 0 <= i <= 9, 那么算法就是O(length of n)。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 
13 vector<char> vs;
14 char s[100001];
15 int k;
16 deque<char> dq;
17 int nxt[10];
18 bool forbid[10]={0};
19 int main()
20 {
21   scanf("%s %d", s, &k);
22   for (int i = 0; i < k; ++i)
23     {
24       int x;
25       scanf("%d", &x);
26       forbid[x] = true;
27     }
28   //preprocess nxt[10]
29   {
30     int p = 0;
31     for (int i = 0; i < 10; ++i)
32       {
33     if (p < i)
34       p = i;
35     while (p < 10 && forbid[p])
36       ++p;
37     if (p == 10)
38       nxt[i] = -1;
39     else
40       nxt[i] = p;
41       }
42   }
43   int len = strlen(s);
44   for (int i = 0; i < len; ++i)
45     dq.push_back(s[i]);
46   for (int i = 0; i < dq.size(); ++i)
47     {
48       if (nxt[dq[i]-'0'] == -1)
49     {
50       int pos = i;
51       --i;
52       while (i >= 0)
53         {
54           if (dq[i] < '9' && nxt[dq[i]-'0'+1]!=-1)
55         {
56           dq[i] = nxt[dq[i]-'0'+1]+'0';
57           break;
58         }
59           --i;
60         }
61       if (i < 0)
62         {
63           for (int j = 0; j < dq.size(); ++j)
64         dq[j] = '0'+nxt[0];
65           dq.push_front(nxt[1]+'0');
66         }
67       else
68         {
69           for (int j = i+1; j < dq.size(); ++j)
70         dq[j] = nxt[0]+'0';
71         }
72       break;
73     }
74       else if (nxt[dq[i]-'0']+'0' > dq[i])
75     {
76       dq[i] = nxt[dq[i]-'0']+'0';
77       for (int j = i+1; j < dq.size(); ++j)
78         dq[j] = nxt[0]+'0';
79       break;
80     }
81     }
82   for (int i = 0; i < dq.size(); ++i)
83     putchar(dq[i]);
84   puts("");
85 }
View Code

 

1974. Iroha and Haiku

題意:給你一個h*w的board和左下角一個a*b的禁止區域(1 <= a < h, 1 <= b < w, 1 <= w, h, <= 1e5),從左上角出發,每次只能向下或向右走,不能走出棋盤。走到右下角的方法數mod 1e9+7。

觀察:可以把board分成(h-a)* w 和 a*(w-b) 兩個長方形,這樣在只需要考慮從第一個長方形進入第二個長方形的位置,在兩個長方形內的路線方法數就是C(長+寬-2, 長-1)。

方法:

把board分成(h-a)* w 和 a*(w-b) 兩個長方形,然后枚舉第一個長方形和第二個長方形相鄰的部分,分別計算兩個長方形內到達這個相鄰位置的方法數,然后相乘,累加到答案

注意!:factorial的大小可能超過1e5!!!!

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 const int maxn = 2e5+1, mod = 1e9+7;
13 ll f[maxn], inv[maxn];
14 ll power(ll base, ll p)
15 {
16   ll ret = 1;
17   while (p)
18     {
19       if (p&1)
20     ret = ret * base % mod;
21       base = base * base % mod;
22       p >>= 1;
23     }
24   return ret;
25 }
26 ll h, w, a, b;
27 ll cb(ll n, ll k)
28 {
29   return f[n] * inv[k] % mod * inv[n-k] % mod;
30 }
31 inline void add(ll &a, ll b)
32 {
33   a = a + b;
34   if (a > mod)
35     a %= mod;
36 }
37 int main()
38 {
39   f[0] = 1;
40   for (ll i = 1; i < maxn; ++i)
41     f[i] = f[i-1] * i % mod;
42   inv[maxn-1] = power(f[maxn-1], mod-2);
43   for (ll i = maxn-1; i >= 1; --i)
44     inv[i-1] = inv[i] * i % mod;
45   scanf("%lld %lld %lld %lld", &h, &w, &a, &b);
46   ll ret = 0;
47   for (ll i = b+1; i <= w; ++i)
48     add(ret, cb(h-a+i-2, h-a-1)*cb(a+w+1-i-2, a-1)%mod);
49   printf("%lld\n", ret);
50 }
View Code

 

1975. Iroha and Haiku

題意:不是很好解釋。大體就是,給定x, y, z (1<= x, z <= 5, 1<= y <= 7),對於一個只由1-10構成的數列,如果存在某個連續子數列,使得可以把這個子數列分成三部分,並且從左到右各部分的元素和為x, y, z, 那么就說這個數列是好的。比如x = 5, y = 7, z = 5, 那么數列1, 2, 3, 3, 4, 5就是好的,因為它從第二個元素到最后一個元素的子數列滿足2+3 = 5, 3+4 = 7, 5 = 5。下面就是給你n<=40, x, y, z,讓你求出好的數列的個數mod 1e9+7。

觀察:比較容易想到反向求解,求不好的數列,然后再用好數列的個數 = 10^n - 不好數列的個數,得到答案。而且對於字符串有一類題目,給定禁止串,求長度為n的串有多少種之類的,和這題的限制很像,我們可以想到定義狀態dp(len, tail),表示當前長度為len,尾部的若干各元素是tail的不好的數列的個數(這里的tail是一個數列)。但是tail的表示和合法性判斷都不是很好做。

方法:(看了題解,但是是日文的,看的不是很懂,如果有理解不對的地方,或是可以改進的地方,請大家指出)

學到了!用二進制數 1 表示 1, 10 表示 2 ,...,  10000表示5, ..., 1000000000 表示10。即一個數i可以用1加上i-1各0表示,就把這種方法成為特殊表示把。那么可以發現,任意幾個相鄰的數的特殊表示相連,一定包含了這幾個數和的特殊表示。比如 2和3相鄰,特殊表示為:10100, 5的特殊表示為10000,被10100包含。所以就可以把tail通過這種特殊表示用二進制數來儲存,並且可以快速判斷是否合法(是否包含x, y, z)。同時注意到x+y+z <= 17,可以只考慮特殊表示的最后18位,2^18不會內存爆炸。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 const int maxn = 20;
13 int x, y, z;
14 int dp[2][1<<maxn] = {0};
15 int f = 0;
16 const int mod = 1e9+7;
17 inline void add(int &a, int b)
18 {
19   a += b;
20   if (a >= mod)
21     a %= mod;
22 }
23 int n;
24 int mask;
25 bool work[1<<maxn] = {0};
26 int main()
27 {
28   scanf("%d", &n);
29   scanf("%d %d %d", &x, &y, &z);
30   int bit = ((((1<<x)+1)<<y)+1)<<(z-1);
31   mask = (1<<(x+y+z))-1;
32   for (int i = 0; i <= mask; ++i)
33     {
34       work[i] = true;
35       int tmp = i;
36       while (tmp)
37     {
38       if ((tmp&bit)==bit)
39         {
40           work[i] = false;
41           break;
42         }
43       tmp >>= 1;
44     }
45     }
46   f = 0;
47   dp[f][0] = 1;
48   for (int i = 0; i < n; ++i)
49     {
50       f ^= 1;
51       memset(dp[f], 0, (mask+1)*sizeof(int));
52       for (int j = 0; j <= mask; ++j)
53     if (work[j])
54       for (int d = 1; d <= 10; ++d)
55         {
56           int nxt = ((j<<1)|1)<<(d-1);
57           nxt &= mask;
58           if (work[nxt])
59         add(dp[f][nxt], dp[f^1][j]);
60         }
61     }
62   int ans = 0;
63   for (int i = 0; i <= mask; ++i)
64     add(ans, dp[f][i]);
65   ll tot = 1;
66   for (int i = 0; i < n; ++i)
67     tot = tot * 10 % mod;
68   tot -= ans;
69   tot %= mod;
70   if (tot < 0)
71     tot += mod;
72   ans = tot;
73   printf("%d\n", ans);
74   
75     
76       
77 }
View Code

 

 

1976. Iroha Loves Strings

題意:

觀察:

方法:

 

1977. Iroha and Haiku (ABC Edition)

題意:給你三個數a, b, c,讓你判斷可不可以通過重新排列順序使三個數變成5, 7, 5

方法:直接暴力。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 int main()
13 {
14   vector<int> v(3);
15   for (auto &e : v)
16     scanf("%d", &e);
17   sort(v.begin(), v.end());
18   vector<int> ans({5, 5, 7});
19   puts(ans==v?"YES":"NO");
20 }
View Code

 

1978. Iroha Loves String (ABC Edition)

題意:給你n<=100個長度均為L<=100的string,記錄為s[1-n],讓你把他們重新排列,使得str = s[1] + s[2] +...+ s[n]的字典序最小。

觀察:經典的考慮兩個相鄰位置的交換是否可以使答案更優。

方法:

考慮答案中相鄰的兩個string, s[i] 和 s[i+1]。 如果s[i]+s[i+1] > s[i+1]+s[i], 那么把兩者交換之后答案的字典恤會更變得更小。因為每個s[j] 的長度都是L,所以s[i]+s[i+1] > s[i+1]+s[i] 和 s[i] > s[i+1] 是等價的。所以只需要把s[] 排一邊序,然后一個個輸出就好了。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 int main()
13 {
14   ios::sync_with_stdio(false);
15   cin.tie(0);
16   int n, l;
17   cin >> n >> l;
18   vector<string> vs(n);
19   for (auto &e : vs)
20     cin >> e;
21   sort(vs.begin(), vs.end());
22   for (auto &e : vs)
23     cout << e;
24   cout << '\n';
25 }
View Code

 

1979. BBQ easy

題意:給你n<=100對木棒,每個木棒的長度都是不超過100的正整數。下面讓你把這2*n個木棒配對,每一對木棒所能承載的肉量等於兩根中較短那根木棒的長度,問你最優配對下一共能承載多少肉。

觀察:貪心

方法:排序,然后貪心即可。

貪心的證明可以先證明最小的兩根配對比不會比不讓最小的兩根配對差,然后問題規模n變為n-1, 數學歸納就好了。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 int main()
12 {
13   int n;
14   scanf("%d", &n);
15   vector<int> L(2*n);
16   for (auto &e : L)
17     scanf("%d", &e);
18   sort(L.begin(), L.end());
19   int ans = 0;
20   for (int i = 0; i < 2*n; i += 2)
21     ans += L[i];
22   printf("%d\n", ans);
23 }
View Code

 

1980. Mysterious light

題意:從一個正三角形的某個位置向其內部發出光線,光線遇到正三角形的邊或者自己過去的軌跡都會反射,直到光線射回初始點。讓你求光路的長度。

觀察:模擬一下,可以發現存在子問題。

方法:可以發現存在一個可以遞歸求解的子問題,就是光在一個平行四邊形區域內走的路程。然后一個類似於Euclidean GCD的函數。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 
12 ll solve(ll x, ll n)
13 {
14   if (n%x==0)
15     {
16       return 2*n-x;
17     }
18   else
19     {
20       ll ret = (n/x)*x*2+solve(n%x, x);
21       return ret;
22     }
23 }
24 
25 ll n, x;
26 ll solve()
27 {
28   return n + solve(x, n-x);
29 }
30 
31 int main()
32 {
33   scanf("%lld %lld", &n, &x);
34   printf("%lld\n", solve());
35 }
View Code

題解指出有一個公式:ans = 3*(n-gcd(n, x)), 還沒有看懂。

 

1981. Shorten Diameter

題意:給你一顆大小為n<=2000的樹,同時給你一個k。問你最少刪多少點,可以讓剩下的圖是一個直徑不超過k的樹。

觀察:n比較小,可以枚舉直徑的中心位置。分k奇偶討論。k為偶數的時候,枚舉中心點,然后保留的點到中心點的距離都不能超過k/2;如果為奇數,就枚舉中心邊。

方法:枚舉中心元素,dfs就好了,O(n^2)

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <queue>
 4 #include <algorithm>
 5 #include <iostream>
 6 #include <vector>
 7 #include <unordered_map>
 8 //#include <bits/stdc++.h>
 9 #include <cassert>
10 #include <map>
11 #include <bitset>
12 using namespace std;
13 
14 #define pb push_back
15 #define mp make_pair
16 typedef long long ll;
17 typedef unsigned long long ull;
18 typedef pair<int, int> ii;
19 
20 const int maxn = 2e3+1;
21 int n, k;
22 vector<int> g[maxn];
23 int dfs(int cur, int pa, int dis, int k)
24 {
25   int ret = 1;
26   if (dis < k)
27     for (auto nxt : g[cur])
28       if (nxt != pa)
29     ret += dfs(nxt, cur, dis+1, k);
30   return ret;
31 }
32 
33 int main()
34 {
35   scanf("%d %d", &n, &k);
36   for (int i = 0; i < n-1; ++i)
37     {
38       int u, v;
39       scanf("%d %d", &u, &v);
40       g[u].pb(v);
41       g[v].pb(u);
42     }
43   int ans = n;
44   if (k%2 == 0)
45     {
46       for (int i = 1; i <= n; ++i)
47     {
48       int tmp = dfs(i, 0, 0, k/2);
49       tmp = n-tmp;
50       if (tmp < ans)
51         ans = tmp;
52 
53     }
54     }
55   else
56     {
57       for (int i = 1; i <= n; ++i)
58     for (auto nxt : g[i])
59       if (nxt > i)
60         {
61           int tmp = dfs(i, nxt, 0, k/2) + dfs(nxt, i, 0, k/2);
62           tmp = n-tmp;
63           if (tmp < ans)
64         ans = tmp;
65         }
66     }
67   printf("%d\n", ans);
68 }
View Code

這道題n比較大的時候(比如1e5)該怎么做呢?

 

1982. Arrays and Palindrome

題意:

觀察:

方法:

 

1983. 

1984.

1995. Range Product

題意:給你兩個int a, b,a <= b,  問你把a, a+1, ...., b-1, b都乘起來,乘積是正是負還是0。

觀察:當且僅當[a, b] 有0的時候乘積才會是0, 不然只有當區間內負數個數為奇數的時候,乘積為負,不然就是正。

方法:直接判斷

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <queue>
 4 #include <algorithm>
 5 #include <iostream>
 6 #include <vector>
 7 #include <unordered_map>
 8 //#include <bits/stdc++.h>
 9 #include <cassert>
10 #include <map>
11 #include <bitset>
12 using namespace std;
13 
14 #define pb push_back
15 #define mp make_pair
16 typedef long long ll;
17 typedef unsigned long long ull;
18 typedef pair<int, int> ii;
19 
20 
21 int cal(int x)
22 {
23   if (x >= 0)
24     return 0;
25   return -x;
26 }
27 
28 int main()
29 {
30   int a, b;
31   scanf("%d %d", &a, &b);
32   if (a <= 0 && 0 <= b)
33     puts("Zero");
34   else
35     {
36       int cnt = cal(a)-cal(b+1);
37       if (cnt%2==0)
38     puts("Positive");
39       else
40     puts("Negative");
41     }
42 }
View Code

想了下其實可以通過負數邊界的奇偶性來判斷負數個數的奇偶性。

 

1996. Box and Ball

題意:給你n <- 1e5個盒子。初始時第一個盒子裝了一個紅球,剩下的盒子每個盒子裝了一個白球。下面給你m<=1e5次操作,每次從一個盒子x中隨機取一個球放到盒子y中。問你最終有多少個盒子可能裝了紅球。

觀察:因為問的是“可能”,我們每次把紅球放進一個盒子,就可以看作把這個盒子里所有的球都染成了紅色。

方法:用has[] 標記該盒子里是否可能有紅球。直接模擬做就好了,注意一個盒子被掏空時,要把has[]設為false。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <queue>
 4 #include <algorithm>
 5 #include <iostream>
 6 #include <vector>
 7 #include <unordered_map>
 8 //#include <bits/stdc++.h>
 9 #include <cassert>
10 #include <map>
11 #include <bitset>
12 using namespace std;
13 
14 #define pb push_back
15 #define mp make_pair
16 typedef long long ll;
17 typedef unsigned long long ull;
18 typedef pair<int, int> ii;
19 
20 const int maxn = 1e5+1;
21 int n, m;
22 int cnt[maxn];
23 bool has[maxn];
24 int main()
25 {
26 
27   int n, m;
28   scanf("%d %d", &n, &m);
29   fill(cnt+1, cnt+n+1, 1);
30   has[1] = 1;
31   for (int i = 0; i < m; ++i)
32     {
33       int x, y;
34       scanf("%d %d", &x, &y);
35       if (has[x])
36     has[y] = true;
37       --cnt[x];
38       ++cnt[y];
39       if (cnt[x] == 0)
40     has[x] = 0;
41     }
42   int ans = 0;
43   for (int i = 1; i <= n; ++i)
44     ans += has[i];
45   printf("%d\n", ans);
46 }
View Code

 

1997. Knot Puzzle

題意:給你n (2 <= n<= 1e5)段繩子,第i段繩子長度為a[i] <= 1e9。一開始,繩子通過結點順序鏈接行程繩索,即第i根的右端通過結點連着第i+1根的左端。告訴你,當一根繩索長度不小於L(<=1e9)的時候,你可以斷開該繩子上任意一個結點,使得繩索分為兩段新的繩索。下面問你,是否能將繩索分為原來的n段繩子。如果可以,請給出方案。

觀察:可以想到,如果有兩段相鄰的繩子長度之和不小於L,那么我們就可以這兩段繩子為基准,一步步斷開最外側的結點,最后再斷開這兩段繩子間的結點。那么這個條件是否是必要的呢?通過思考得到,應該是必要的。反證,如果任意兩段相鄰的繩子長度之和都小於L,而且能夠達成題意的目標,那么最后一步我們一定會得到一段繩索,它是由兩個相鄰繩子組成,那么我們將無法切斷它,因為相鄰兩段繩子的長度之和小於L,所以無法達成目標,與假設相悖。

方法:枚舉找出上述方法中的基准,然后從外層向內一個個切斷,最后再把基准切開。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <queue>
 4 #include <algorithm>
 5 #include <iostream>
 6 #include <vector>
 7 #include <unordered_map>
 8 //#include <bits/stdc++.h>
 9 #include <cassert>
10 #include <map>
11 #include <bitset>
12 using namespace std;
13 
14 #define pb push_back
15 #define mp make_pair
16 typedef long long ll;
17 typedef unsigned long long ull;
18 typedef pair<int, int> ii;
19 
20 
21 
22 const int maxn = 1e5+1;
23 int n, a[maxn], l;
24 
25 int main()
26 {
27   scanf("%d %d", &n, &l);
28   for (int i = 0; i < n; ++i)
29     {
30       scanf("%d", a+i);
31     }
32   int tar = -1;
33   for (int i = 0; i < n-1; ++i)
34     if (a[i]+a[i+1]>=l)
35       {
36     tar = i;
37     break;
38       }
39   if (tar == -1)
40     {
41       puts("Impossible");
42       return 0;
43     }
44   puts("Possible");
45   tar += 1;
46   for (int i = 1; i < tar; ++i)
47     printf("%d\n", i);
48   for (int i = n-1; i > tar; --i)
49     printf("%d\n", i);
50   printf("%d\n", tar);
51 }
View Code

 

1998. Stamp Rally

題意:給一個n<=1e5個點,m<=1e5條邊的無向圖。q<=1e5個詢問,每次形如x, y, z (1<= x, y, z <= n),表示從x或者從y沿邊擴展到大小為z的子圖中(包含x和y的大小為z的子圖,而且每個點在子圖中必須與x或者y聯通),所包含的路徑最大編號最小可以是多少。

觀察:最優性問題轉化成判定性問題,即只考慮前i條邊,是否可以滿足存在一個由x,y擴展的大小為z的子圖。我們只需要維護dsu,不斷加邊,如果一個時刻,x和y所在聯通分量的大小之和不小於z,那么就存在一個大小剛好為z的子圖。如果只有一個詢問,把邊按照從小到大的順序加進去,並且每次加完檢查是否存在合法子圖就好,這樣單次查詢O(n)。但是詢問多組,我們想到如果可以快速的二分答案,那么這樣總時間就是O(q*log(n))。但是每次二分判定需要特殊的數據結構記錄下每一個時刻(即加完一條邊)dsu的情況(可持久化?),不容易實現。所以我們想到整體來做,大體就是整體二分的思想。

方法:考慮一個函數 solve({q}, ansl, ansr, depth) 表示當前{q}中詢問的答案所在的范圍是[ansl, ansr](depth是二分的深度,輔助接下來的解釋用的),那么另ansmid = (ansl+ansr)/2。我們把ansmid之前的邊merge一下,然后對於{q}中的每個詢問,檢查一下當前x,y,z是否有解;有解的話把該詢問放到{qleft}, 不然放到{qright}。然后遞歸求解solve({qleft}, ansl, ansmid, depth+1), solve({qright}, ansmid+1, ansr, depth+1)。注意在同一個depth(二分樹的同一個深度),所有邊只需被merge一次,所以我們可以按照深度順序來處理solve()函數,大致就是bfs。

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <queue>
  4 #include <algorithm>
  5 #include <iostream>
  6 #include <vector>
  7 #include <unordered_map>
  8 //#include <bits/stdc++.h>
  9 #include <cassert>
 10 #include <map>
 11 #include <bitset>
 12 using namespace std;
 13 
 14 #define pb push_back
 15 #define mp make_pair
 16 typedef long long ll;
 17 typedef unsigned long long ull;
 18 typedef pair<int, int> ii;
 19 
 20 
 21 const int maxn = 1e5+1;
 22 int n, m, qcnt;
 23 vector<int> g[maxn];
 24 int p[maxn], cnt[maxn];
 25 void clear()
 26 {
 27     for (int i = 1; i <= n; ++i)
 28         p[i] = i, cnt[i] = 1;
 29 }
 30 int pa(int id)
 31 {
 32     return id==p[id]?id:p[id]=pa(p[id]);
 33 }
 34 void merge(int x, int y)
 35 {
 36     x = pa(x);
 37     y = pa(y);
 38     if (x != y)
 39     {
 40         cnt[y] += cnt[x];
 41         p[x] = y;
 42     }
 43 }
 44 struct Query
 45 {
 46     int l, r, z, id;
 47     inline void read(int i)
 48     {
 49         scanf("%d %d %d", &l, &r, &z);
 50         id = i;
 51     }
 52 } q[maxn];
 53 int ans[maxn];
 54 ii e[maxn];
 55 int main()
 56 {
 57     scanf("%d %d", &n, &m);
 58     for (int i = 1; i <= m; ++i)
 59     {
 60         scanf("%d %d", &e[i].first, &e[i].second);
 61     }
 62     scanf("%d", &qcnt);
 63     for (int i = 0; i < qcnt; ++i)
 64         q[i].read(i);
 65     queue<Query> que;
 66     que.push((Query){0, qcnt-1, 1, m});
 67     int tot = 0;
 68     while (!que.empty())
 69     {
 70         clear();
 71         int sz = que.size();
 72         int ptr = 1;
 73 //        cerr << "level " << ++tot << endl;
 74         for (int t = 0; t < sz; ++t)
 75         {
 76             auto cur = que.front();
 77             que.pop();
 78 //            cerr << "solve " << " " << cur.l << " " << cur.r << " " << cur.z << " " << cur.id << endl;
 79             if (cur.z == cur.id)
 80             {
 81                 for (int i = cur.l; i <= cur.r; ++i)
 82                     ans[q[i].id] = cur.z;
 83                 continue;
 84             }
 85             int tm = (cur.z+cur.id)>>1;
 86             while (ptr <= tm)
 87             {
 88                 merge(e[ptr].first, e[ptr].second);
 89                 ++ptr;
 90             }
 91             int l = cur.l, r = cur.r;
 92 //            cerr << "till " << tm << endl;
 93             for (int i = l; i <= r; ++i)
 94             {
 95                 int a = q[i].l, b = q[i].r;
 96                 if ((pa(a)==pa(b) && cnt[pa(a)] >= q[i].z) || (pa(a) != pa(b) && cnt[pa(a)]+cnt[pa(b)]>=q[i].z))
 97                 {
 98 //                    cerr << a << " " << b << " cnt >= " << q[i].z << endl;
 99                 }
100                 else
101                 {
102 //                    cerr << a << " " << b << " cnt < " << q[i].z << endl;
103                     swap(q[i--], q[r--]);
104                 }
105             }
106             if (cur.l <= r)
107                 que.push((Query){cur.l, r, cur.z, tm});
108             if (r+1<=cur.r)
109                 que.push((Query){r+1, cur.r, tm+1, cur.id});
110         }
111     }
112     for (int i = 0; i < qcnt; ++i)
113         printf("%d\n", ans[i]);
114 }
View Code

 

1999. 

2000.

2001. Wanna go back home

題意:給你一個長度不超過1000的string,由NWSE組成,意思為向某個方向走任意positive長度的距離。問你有沒有可能回到原地。

觀察:因為向一個方向走的距離可以任選,所以只要一個維度上正反兩個方向都出現過,那就一定可以走回0。 

方法:統計各個方向是否出現,同一個維度上兩個方向必須同時出現或者同時不出現,否則無法走回原地。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 bool vis[300] = {0};
12 char str[1001];
13 int main()
14 {
15   char *s = str;
16   scanf("%s", s);
17   while (*s)
18     {
19       vis[*s] = true;
20       ++s;
21     }
22   puts((vis['N']^vis['S'])||(vis['E']^vis['W'])?"No":"Yes");
23 }
View Code

 

2002. Simplified mahjong

題意:告訴你n<=1e5種牌,每種牌的個數A[i] <=1e9。然后如果牌號之差不超過1,那么兩個牌可以組成一個對子。問你最多可以租多少個對子。

觀察:貪心可行,從牌號最小的牌開始,剩下的牌就留着和下個編號的牌配對。證明也是可以反證,找到編號最小的未配對的牌,假設有比他小1的未配對的牌,那么可以產生新的對子;如果有和他編號相同但是和比他大一號的牌配對的牌,那么拆散他倆,讓兩個編號相同的牌配對,不會使原來的對數減少。如果有比他編號大的牌,進行類似的操作。然后會發現我們的配對方式不會使答案變差。

方法:按照思路實現即可

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define mp make_pair
 5 #define pb push_back
 6 
 7 typedef long long ll;
 8 typedef pair<int, int> ii;
 9 typedef pair<ll, ll> l4;
10 
11 int n;
12 int main()
13 {
14   ll ans = 0, last = 0, a;
15   scanf("%d", &n);
16   for (int i = 0; i < n; ++i)
17     {
18       scanf("%lld", &a);
19       ll take = min(last, a);
20       ans += take;
21       a -= take;
22       ans += a/2;
23       last = a%2;
24     }
25   printf("%lld\n", ans);
26 
27 }
View Code

 

 

 

3xxx. Multiple Gift

題意:給你1<= x <= y <= 1e18, 讓你找出一個最長的序列A,使得 x <= A[i] <= y 並且 A[i+1] % A[i] == 0。

觀察:可以貪心。

方法:去x, 2*x, 4*x, ... 直到突破天際。復雜度是log2(y) 的。浪一點可以推個公式,答案是滿足x * 2^n > y 最小的n,即floor(log2(y/x)+1)

 

3xxx. Wide Flip

題意:給你一個長度為n<= 1e5的01串。對於一個K,你可以反轉任意長度大於等於k區間內的數字。問你能使原來01串全變成0的最大k是多少。

觀察:有點難搞,猜測答案存在單調性以至於可以二分。單調性比較明顯,因為對於任意的k = k1, 如果可以達到目標,那么k = k2 < k1 也一定能達到目標,因為k = k1 所對應的操作集合是k = k2對應操作集合的子集。下面就是考慮該如何短時間內判定。設當前k = mid, 01串從1開始編號(1-indexed) ,那么對於[1, n-mid+1] 的位置, 我們從左到右貪心的反轉,那么每次反轉多長的區間呢?我使用的長度為mid的區間, 即[i, i+mid-1],但好像用最長的區間也沒有關系,即[i, n]。下面對於[n-mid+2, n]的位置,我們希望可以在不改變之前元素的前提下,改變他們的值。根據第一個樣例可以發現,有一種特殊的操作,任意可以更改“靠后”位置的值且不影響其他值,比如你想改變第j位的值,那么先反轉[j-mid, j], 再反轉[j-mid, j-1]。所以,當且僅當 j-mid>=1 時,我們可以任意改變j的值。所以就是檢查[n-mid+2, n]里面的值,如果有1的話,看看可不可以通過特殊操作“任意改變”。或者直接看[n-mid+2, mid] 這個區間里是否有1, 有1的話就還原不了,沒有的話就可以還原。

 

3xxx. Papple Sort

題意:給你一個長度為n<=2e5的字符串s,只由小寫字母組成。下面問你,可否指通過交換相鄰的兩個字母,把s變成一個回文串。如果可以的話,最少要交換幾次?

觀察:首先,判斷是否能達成比較簡單,只需統計各個字母出現次數就好了,能達成回文的充要條件是,至多只有一個字母出現奇數次。下面我們考慮,怎樣構造才能使交換次數最小。比賽的時候自己沒有證明,想到這么幾個點。首先,同一種字母的相對順序是不會變的;其次,好像先確定最外層元素會容易一點,因為確定了外層元素之后,確定了的元素對未確定的元素就沒有影響了。然后取得時候我貪心的取最外層的元素,但是沒有證明。題解中給了證明,說兩種字符A,B之間的相對位置,只有當ABBA的時候要先取A,BAAB的時候先取B,其他時候都無所謂。

方法:

3xxx. Christmas Tree

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM