Noip2015提高组解题报告
Day1
T1神奇的幻方
一道简单异常的小模拟,我们只需要确定数字1的位置,然后根据题意枚举即可,简简单单就A了,什么也不卡。
然而这题,我刚开始学OI的时候,因为当时比较蠢,被这题花式吊打啊....根本不会啊.....
ε=(´ο`*)))唉又想起没学OI的自己了..
虽然题简单,还是惯例丢代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 60; 4 int a[maxn][maxn]; 5 int n, x, y; 6 7 inline int read() { 8 int x = 0, y = 1; 9 char ch = getchar(); 10 while(!isdigit(ch)) { 11 if(ch == '-') y = -1; 12 ch = getchar(); 13 } 14 while(isdigit(ch)) { 15 x = (x << 1) + (x << 3) + ch - '0'; 16 ch = getchar(); 17 } 18 return x * y; 19 } 20 21 int main() { 22 n = read(); 23 x = 1, y = n / 2 + 1; 24 a[x][y] = 1; 25 for(register int i = 2; i <= n * n; ++i) { 26 if(x == 1 && y != n) { 27 x = n, y += 1; 28 a[x][y] = i; 29 } 30 else if(x != 1 && y == n) { 31 x -= 1, y = 1; 32 a[x][y] = i; 33 } 34 else if(x == 1 && y == n) { 35 x += 1; 36 a[x][y] = i; 37 } 38 else if(x != 1 && y != n) { 39 if(!a[x - 1][y + 1]) { 40 x -= 1, y += 1; 41 a[x][y] = i; 42 } 43 else if(a[x - 1][y + 1]) { 44 x += 1; 45 a[x][y] = i; 46 } 47 } 48 } 49 for(register int i = 1; i <= n; ++i) { 50 for(register int j = 1; j <= n; ++j) 51 printf("%d ", a[i][j]); 52 printf("\n"); 53 } 54 return 0; 55 }
T2信息传递
大意就是说一堆人会一直传话,形成一个环,问你最小的环里有多少个人
显然你可以直接tarjan跑强联通分量,当然你也可以跑并查集等做法做
并查集写法简单讲就是在路径压缩同时维护环的大小,对于给出的传递者与被传递者,判断是不是一个集合里的,不是就合并
是就更新答案
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define uint unsigned int 4 #define ull unsigned long long 5 using namespace std; 6 const int maxn = 200010; 7 const int inf = 1000000007; 8 int t, fa[maxn]; 9 int dis[maxn], ans; 10 int n; 11 12 inline int read() { 13 int x = 0, y = 1; 14 char ch = getchar(); 15 while(!isdigit(ch)) { 16 if(ch == '-') y = -1; 17 ch = getchar(); 18 } 19 while(isdigit(ch)) { 20 x = (x << 1) + (x << 3) + ch - '0'; 21 ch = getchar(); 22 } 23 return x * y; 24 } 25 26 int getfather(int x) { 27 if(x == fa[x]) return x; 28 int son_in_son = fa[x]; 29 fa[x] = getfather(fa[x]); 30 dis[x] += dis[son_in_son]; 31 return fa[x]; 32 } 33 34 int main() { 35 // freopen("message.in", "r", stdin); 36 // freopen("message.out", "w", stdout); 37 n = read(); 38 for(register int i = 1; i <= n; ++i) fa[i] = i; 39 ans = inf; 40 for(register int i = 1; i <= n; ++i) { 41 t = read(); 42 int u = getfather(i), v = getfather(t); 43 if(u != v) { 44 fa[u] = v; 45 dis[i] = dis[t] + 1; 46 } 47 else ans = min(ans, dis[i] + dis[t] + 1); 48 } 49 printf("%d\n", ans); 50 return 0; 51 }
tarjan就更简单了,跑强连通分量,统计每个环中节点的大小,然后找最小的大小不为1环的就好了
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define uint unsigned int 4 #define ull unsigned long long 5 using namespace std; 6 const int maxn = 200010; 7 const int inf = 1000000007; 8 struct shiki { 9 int net, y; 10 }e[maxn << 1]; 11 int lin[maxn], len = 0; 12 int dfn[maxn], low[maxn]; 13 int num = 0, cnt = 0, top = 0; 14 int c_num[maxn], s[maxn]; 15 bool in_s[maxn]; 16 int sum[maxn]; 17 int n, t, ans; 18 19 inline int read() { 20 int x = 0, y = 1; 21 char ch = getchar(); 22 while(!isdigit(ch)) { 23 if(ch == '-') y = 1; 24 ch = getchar(); 25 } 26 while(isdigit(ch)) { 27 x = (x << 1) + (x << 3) + ch - '0'; 28 ch = getchar(); 29 } 30 return x * y; 31 } 32 33 inline void insert(int xx, int yy) { 34 e[++len].y = yy; 35 e[len].net = lin[xx]; 36 lin[xx] = len; 37 } 38 39 void tarjan(int x) { 40 dfn[x] = low[x] = ++num; 41 s[++top] = x, in_s[x] = 1; 42 for(int i = lin[x]; i; i = e[i].net) { 43 int to = e[i].y; 44 if(!dfn[to]) { 45 tarjan(to); 46 low[x] = min(low[x], low[to]); 47 48 } 49 else if(in_s[to]) low[x] = min(low[x], dfn[to]); 50 } 51 if(dfn[x] == low[x]) { 52 cnt++; int k; 53 do { 54 k = s[top--], in_s[k] = 0; 55 c_num[k] = cnt; 56 }while(x != k); 57 } 58 } 59 60 int main() { 61 memset(sum, 0, sizeof(sum)); 62 n = read(); 63 for(register int i = 1; i <= n; ++i) { 64 t = read(); 65 insert(i, t); 66 } 67 for(register int i = 1; i <= n; ++i) 68 if(!dfn[i]) tarjan(i); 69 for(register int i = 1; i <= n; ++i) 70 sum[c_num[i]]++; 71 ans = inf; 72 for(register int i = 1; i <= cnt; ++i) 73 if(sum[i] != 1) ans = min(ans, sum[i]); 74 printf("%d\n", (ans != inf) ? ans : 1); 75 return 0; 76 }
T3斗地主
这题当年显然恶心的不少的人
这题确实比较恶心,尤其是那诡异的一堆牌的出法,因为和真实斗地主不一样,比较不适
对于这点,我们本着:“所有题面没说是不合法的情况,都是合法的” 的原则,可以知道最烦人的大小王,他们不是对,他们是单牌,所以炸弹带大小王是合法的!
因为炸弹可以带两张单牌。
我们贪心的想一想,显然我们要想出牌次数最小,一定是要尽可能的先把所有能一次丢走顺子和带牌都出完,最后剩下的牌在甩完就好,所以我们可以爆搜顺子和带牌加上最后剩下的牌的出牌次数,答案求min
好吧和贪心并没有什么关系,我们做这题首先要有一个合理的搜索顺序:先搜顺子和带牌,最后处理剩余的牌
因为显然,除了顺子,带牌,剩下的无论是什么都可以一次出完,而相比枚举出掉什么单牌或对子或炸弹
显然顺子和带牌的情况更方便处理,这样我们就可以爆搜了,奥对,顺子和带牌先搜哪个后搜哪个都是可以A题的
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 25; 4 const int inf = 1000000007; 5 int sum[maxn]; 6 int T, n, ans; 7 8 inline int read() { 9 int x = 0, y = 1; 10 char ch = getchar(); 11 while(!isdigit(ch)) { 12 if(ch == '-') y = -1; 13 ch = getchar(); 14 } 15 while(isdigit(ch)) { 16 x = (x << 1) + (x << 3) + ch - '0'; 17 ch = getchar(); 18 } 19 return x * y; 20 } 21 22 void dfs_kill(int x) {//出牌次数 23 /* 24 可以公开的情报: 25 出牌方式有火箭,炸弹,单牌,对牌,三不带,三带单,三带对, 26 顺子,连对,三顺, 四带二(且带的两张牌不要求相同) 27 */ 28 if(x >= ans) return; 29 //顺子势力 30 int op = 0;//单顺 31 for(register int i = 3; i <= 14; ++i) {//2与双王不可用 32 if(sum[i] < 1) op = 0;//打断顺子 33 else { 34 op++;//长度加1 35 if(op >= 5) { 36 for(register int j = i - op + 1; j <= i; ++j) sum[j]--;//出牌 37 dfs_kill(x + 1); 38 for(register int j = i - op + 1; j <= i; ++j) sum[j]++;//回溯 39 } 40 } 41 } 42 op = 0;//连对 43 for(register int i = 3; i <= 14; ++i) { 44 if(sum[i] < 2) op = 0;//打断连对 45 else { 46 op++; 47 if(op >= 3) { 48 for(register int j = i - op + 1; j <= i; ++j) sum[j] -= 2; 49 dfs_kill(x + 1); 50 for(register int j = i - op + 1; j <= i; ++j) sum[j] += 2; 51 } 52 } 53 } 54 op = 0;//三顺 55 for(register int i = 3; i <= 14; ++i) { 56 if(sum[i] < 3) op = 0; 57 else { 58 op++; 59 if(op >= 2) { 60 for(register int j = i - op + 1; j <= i; ++j) sum[j] -= 3; 61 dfs_kill(x + 1); 62 for(register int j = i - op + 1; j <= i; ++j) sum[j] += 3; 63 } 64 } 65 } 66 //带牌 67 for(register int i = 2; i <= 14; ++i) {//大小王不能带牌 68 if(sum[i] < 3) continue;//连三带都不行的 69 sum[i] -= 3;//大家都先搞三带 70 for(register int j = 2; j <= 16; ++j) {//三带一居然能带大小王?? 71 if(sum[j] < 1 || j == i) continue; 72 sum[j]--; 73 dfs_kill(x + 1); 74 sum[j]++; 75 } 76 for(register int j = 2; j <= 14; ++j) {//三带二,大小王不算对子 77 if(sum[j] < 2 || j == i) continue; 78 sum[j] -= 2; 79 dfs_kill(x + 1); 80 sum[j] += 2; 81 } 82 sum[i] += 3; 83 if(sum[i] > 3) {//一些群众可以四带 84 sum[i] -= 4; 85 for(register int j = 2; j <= 15; ++j) {//带单牌之时,大小王算单牌 86 if(sum[j] < 1 || j == i) continue; 87 sum[j]--; 88 for(register int k = 2; k <= 15; ++k) { 89 if(sum[k] < 1 || (k == j && k != 15) || k == i) continue; 90 sum[k]--; 91 dfs_kill(x + 1); 92 sum[k]++; 93 } 94 sum[j]++; 95 } 96 for(register int j = 2; j <= 14; ++j) {//带双牌之时,大小王不算对子 97 if(sum[j] < 2 || j == i) continue; 98 sum[j] -= 2; 99 for(register int k = 2; k <= 14; ++k) { 100 if(sum[k] < 2 || k == j || k == i) continue; 101 sum[k] -= 2; 102 dfs_kill(x + 1); 103 sum[k] += 2; 104 } 105 sum[j] += 2; 106 } 107 sum[i] += 4; 108 } 109 } 110 //已经处理完了顺子,连对,三顺,三带一,三带二,四带二单,四带二对 111 //对于剩下的势力,显然可以一次性丢出去 112 for(register int i = 2; i <= 15; ++i) if(sum[i]) x++; 113 ans = min(ans, x); 114 } 115 116 int main() { 117 // freopen("landlords.in", "r", stdin); 118 // freopen("landlords.out", "w", stdout); 119 T = read(), n = read(); 120 while(T--) { 121 memset(sum, 0, sizeof(sum)); 122 ans = inf; 123 for(register int i = 1; i <= n; ++i) { 124 int which = read(), col = read(); 125 if(which == 0) sum[15]++;//大小王放在同一个位置 126 else if(which == 1) sum[14]++;//塞进一个A,因为A可以丢进顺子等组合且比较大,放在后面 127 else sum[which]++; 128 } 129 dfs_kill(0); 130 printf("%d\n", ans); 131 } 132 return 0; 133 }
这样我们就把Noip2015Day1给AK了,实际上就这套题的难度来看,前两题简直是送分,我写前两题甚至没超过半个小时(大概?)
最后T3确定好规则和搜索顺序,因为数据随机,所以直接爆搜并不难过。这样,你就有个极大的优势了
Day2
跳石头
挺经典的二分答案题目(不)
二分一个距离,然后开始判定是否合法:
定义一个变量now表示现在在哪块石头上,ans表示能拿掉的石头数量
若枚举到的石头i与now的距离小于二分出的距离,将ans++;
否则令now = i;
若最后ans <= m,则距离不够(显然会有人想知道为什么ans==m不行,我们想一下,我们能够跳到一块石头为i+m,那么按照我们的判定方式,就将i+m拿掉了,然而我们能不能跳到第i+m+1块石头呢?因为必然要有一块岩石做终点,不能跳到水里,所以我们至少要能够拿掉m+1块石头才能说明我们一定能拿掉m块石头并且保证有起点和终点)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 50010; 4 int a[maxn]; 5 int L, m, n; 6 7 inline int read() { 8 int x = 0, y = 1; 9 char ch = getchar(); 10 while(!isdigit(ch)) { 11 if(ch == '-') y = -1; 12 ch = getchar(); 13 } 14 while(isdigit(ch)) { 15 x = (x << 1) + (x << 3) + ch - '0'; 16 ch = getchar(); 17 } 18 return x * y; 19 } 20 21 inline bool check(int x) { 22 int ans = 0, now = 0; 23 for(int i = 1; i <= n; ++i) 24 if(a[i] - now < x) ans++; 25 else now = a[i]; 26 return ans <= m; 27 } 28 29 int main() { 30 L = read(), n = read(), m = read(); 31 for(int i = 1; i <= n; ++i) a[i] = read(); 32 int l = 0, r = L; 33 while(l < r) { 34 int mid = l + r >> 1; 35 if(check(mid)) l = mid + 1; 36 else r = mid - 1; 37 } 38 if(!check(l)) l -= 1; 39 printf("%d\n", l); 40 return 0; 41 }
子串
这题,还挺好写的....
高端做法不会,但是我们可以很容易的想到一个四维的状态
f[i, j, k, 0/1]表示A串取到了第i个字符,B串匹配了j个字符,使用了k个子串,第i个字符取或是不取
方程:
f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1]) % mod
如果ai != bj 则 f[i][j][k][1] = 0
否则 f[i][j][k][1] = (f[i-1][j - 1][k][1] + (f[i-1][j - 1][k - 1][0] + f[i-1][j - 1][k - 1][1]
因为空间问题(毕竟是四维嘛...)我们使用滚动数组即可
初态:f[0][0][0][0] = f[1][0][0][0] = 1
末态:f[n][m][k][0]+f[n][m][k][1]
然后就简简单单地A题了
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int mod = 1000000007; 4 const int maxn = 1010; 5 const int maxm = 210; 6 int f[2][maxm][maxm][2]; 7 char a[maxn], b[maxm]; 8 int n, m, l, id = 1; 9 10 inline int read() { 11 int x = 0, y = 1; 12 char ch = getchar(); 13 while(!isdigit(ch)) { 14 if(ch == '-') y = -1; 15 ch = getchar(); 16 } 17 while(isdigit(ch)) { 18 x = (x << 1) + (x << 3) + ch - '0'; 19 ch = getchar(); 20 } 21 return x * y; 22 } 23 24 int main() { 25 // freopen("a.in", "r", stdin); 26 // freopen("a.out", "w", stdout); 27 n = read(), m = read(), l = read(); 28 scanf("%s%s", a + 1, b + 1); 29 f[0][0][0][0] = f[1][0][0][0] = 1; 30 for(register int i = 1; i <= n; ++i) { 31 for(register int j = 1; j <= m; ++j) 32 for(register int k = 1; k <= l; ++k) { 33 f[i&1][j][k][0] = (f[(i-1)&1][j][k][0] + f[(i-1)&1][j][k][1]) % mod; 34 if(a[i] != b[j]) f[i&1][j][k][1] = 0; 35 else f[i&1][j][k][1] = (f[(i-1)&1][j - 1][k][1] + (f[(i-1)&1][j - 1][k - 1][0] + f[(i-1)&1][j - 1][k - 1][1]) % mod) % mod; 36 } 37 } 38 printf("%d\n", (f[n & 1][m][l][0] + f[n & 1][m][l][1]) % mod); 39 return 0; 40 }
运输计划
两天来最难的题,当然对于一些大佬,这题比斗地主简单
题解:LCA+差分+二分
先咕咕咕一下回头一定补咕咕咕咕