Day1
T1神奇的幻方
一道简单异常的小模拟,我们只需要确定数字1的位置,然后根据题意枚举即可,简简单单就A了,什么也不卡。
然而这题,我刚开始学OI的时候,因为当时比较蠢,被这题花式吊打啊....根本不会啊.....
ε=(´ο`*)))唉又想起没学OI的自己了..
虽然题简单,还是惯例丢代码
1 #include2 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 #include2 #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 #include2 #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 #include2 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 }
这样我们就把Noip2015给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 #include2 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 #include2 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+差分+二分
先咕咕咕一下回头一定补咕咕咕咕