T1 园艺工人的求助
【题目描述】
终于,在一段繁忙的训练之后,到了NOIP 的举办的时候。同学们坐上了大巴车,享受着沿途的风光,讨论着未解决的问题,憧憬着NOIP 赛场上切题的样子。很快,大
巴车到了大连大学科技楼,也就是辽宁NOIP 的举办地点。大连大学科技楼是一幢宏伟的建筑,楼前摆放有一排花,共有n 盆。花一共只有26 种,分别用26 个小写英文
字母表示,也就是说,楼前的这排花可以用一个仅包含小写英文字母的字符串表示。大连大学雇了一个园艺工人,专门打理科技楼前的花。园艺工人看见你,热情地向你打招呼:“NOIP 加油!”其实,他是有问题想请你帮忙呢!现在园艺工人想再购买一盆花(可以任选26 种花中的一种),插入到原来的花中间(可以放在整排花的最左侧与最右侧),他想知道在插入一盆花后,能否使整排花左右对称。例如,ababa 是左右对称的,而abcd 不是。注意:即使原来的一排花已经是左右对称的,也必须再插入一盆花。
【输入格式】
从文件flower.in 中读入数据。
本题目有多组数据,输入第一行为一个正整数t,表示数据组数。
接下来t 行,每行包含一个正整数n 和一个长度为n 的字符串,分别表示花的数量与花构成的序列。
【输出格式】
输出到文件flower.out 中。
对于每组数据输出一行。若再插入一盆花之后能使整排花左右对称,输出Yes,否则输出No(注意大小写)。
【样例1 输入】
4
4 abcd
4 aabb
4 aaaa
10 abcdefecba
【样例1 输出】
No
No
Yes
Yes
【子任务】
对于20% 的数据,n <= 5;
对于另30% 的数据,t = 1 且n <= 1000;
对于100% 的数据,Σn <= 106。
题面好像就最后两句话有用。
就是说给一个字符串,再插入一个字符后能否成为一个回文串。
首先,如果他是回文串,那么插入一个字符后一定还是回文串,这个想想就明白了。
如果不是回文串,因为Σn <= 10 6,所以对于每一个字符串,我们暴力的从头L和尾R两个端点开始一个一个比对就行。然后遇到一个不一样的字符,我们需要考虑是往L那一端插入字符还是往R端插,分情况讨论即可,然后如果两种方案都又遇到不相等的字符,就输出No了,而其中一种方案成立,就Yes。
代码实现时,插入字符可以理解为这一端的指针向前跳一个字符。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter printf("\n") 13 #define space printf(" ") 14 #define Mem(a) memset(a, 0, sizeof(a)) 15 typedef long long ll; 16 typedef double db; 17 const int INF = 0x3f3f3f3f; 18 const int eps = 1e-8; 19 const int maxn = 1e6 + 5; 20 inline ll read() 21 { 22 ll ans = 0; 23 char ch = getchar(), last = ' '; 24 while(!isdigit(ch)) {last = ch; ch = getchar();} 25 while(isdigit(ch)) 26 { 27 ans = ans * 10 + ch - '0'; ch = getchar(); 28 } 29 if(last == '-') ans = -ans; 30 return ans; 31 } 32 inline void write(ll x) 33 { 34 if(x < 0) x = -x, putchar('-'); 35 if(x >= 10) write(x / 10); 36 putchar(x % 10 + '0'); 37 } 38 39 int t; 40 char a[maxn]; 41 42 int main() 43 { 44 freopen("flower.in", "r", stdin); 45 freopen("flower.out", "w", stdout); 46 t = read(); 47 while(t--) 48 { 49 int n = read(); scanf("%s", a + 1); 50 int cnt = 0; 51 for(int i = 1, j = n; i < j; ++i, --j) 52 { 53 if(a[i] != a[j]) 54 { 55 j++; cnt++; 56 if(cnt > 1) break; 57 } 58 } 59 if(cnt < 2) {printf("Yes\n"); continue;} 60 cnt = 0; 61 for(int i = 1, j = n; i < j; ++i, --j) 62 { 63 if(a[i] != a[j]) 64 { 65 i--; cnt++; 66 if(cnt > 1) break; 67 } 68 } 69 printf("%s\n", cnt < 2 ? "Yes" : "No"); 70 } 71 return 0; 72 }
T2 主席的请求
【题目描述】
再大连大学园艺工人的祝福下,你顺利地结束了NOIP 第一天的比赛。走出考场,漫步于大连大学美丽而静谧的校园中,你感到心情无比舒畅。正当你向大连大学宾馆走
去的时候,一个陌生人叫住了你。原来他是大连大学校园规划部主席,他早知道你是一个无所不能的OIer,并且尤其擅长道路规划。他向你请教了一个问题,希望你能给出答案。
大连大学可以看做一个由n 个结点,m 条有向道路构成的有向图。结点编号为1到n,每条道路有一个长度(为正整数)。其中,科技楼在s 号结点,大连大学宾馆在t
号结点。主席想知道,校园中的每条道路是否一定出现在从s 到t 的最短路径上,如果不一定,那么至少要将这条道路的长度减少多少,才能使这条道路一定出现在从s 到t的最短路径上(减少后的长度必须为一个正整数)。
【输入格式】
从文件road.in 中读入数据。
输入第一行包含4 个正整数n, m, s, t (n <= 2 * 105, m <= 4 * 105, 1 <= s, t <= n),含义如题。
接下来m 行,每行3 个正整数u, v, w(1 <= u, v <= n, w > 0),分别表示一条边的起点、终点、长度。
保证从s 能到达t。
【输出格式】
输出到文件road.out 中。
输出m 行。每行代表一条边的答案:
• 若该边一定出现在从s 到t 的最短路上,输出YES;
• 若该边不一定出现在从s 到t 的最短路上,但能通过改变该边长度为一正整数,使得该边一定出现在从s 至t 的最短路上,输出CAN 和一个正整数,用一个空格隔开,其中的正整数表示该边长度减少的最小值;
• 否则,输出NO。
【样例1 输入】
6 7 1 6
1 2 2
1 3 10
2 3 7
2 4 8
3 5 3
4 5 2
5 6 1
【样例1 输出】
YES
CAN 2
CAN 1
CAN 1
CAN 1
CAN 1
YES
【样例2 输入】
2 2 1 2
1 2 1
1 2 2
【样例2 输出】
YES
NO
【子任务】
对于15% 的数据,n = 5,m <=10;
对于另10% 的数据,整个图是一条链,即1 向2 连边,2 向3 连边,. . . . . . ,n - 1向n 连边,有且仅有这样的测试点n ≡ 9 (mod 10);
对于另20% 的数据,整个图是一棵有根树,其中s 是根,所有边由父节点指向子结点,有且仅有这样的测试点n ≡ 8 (mod 10);
对于另25% 的数据,n <= 1000,m <= 2000。
首先我们考虑每一条边,如果他一定在最短路上,就输出YES;如果可能在最短路上,那只要-1就一定是在最短路上了;如果不在最短路上,那我们只要让这条边变小,直到经过这条边的最短路径成为新的最短路就行了。
那么我们下面具体考虑怎么实现:
首先解决他是否在最短路上,只要正着跑一遍dijkstra然后反着跑一遍dijkstra就行了。正着跑得到dis1[i]表示起点 s 到 i 的最短路,同理dis2[i]表示终点t到 i 的最短路。那么经过从u到v的一条边的最短路就可以表示为dis1[u] + w + dis2[v],如果他和dis1[t],即最短路相等,那么他就可能在最短路上,否则一定不在。
接下来要判断的是w这条边是一定在最短路上还是可能在最短路上。首先我们得会最短路计数,参见我的另一篇博文,如果num1[u] * num2[v] == num1[t](总最短路条数)的话,就说明一定在图的最短路上了。
这样每一种情况就解决了。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter printf("\n") 13 #define space printf(" ") 14 #define Mem(a) memset(a, 0, sizeof(a)) 15 typedef long long ll; 16 typedef double db; 17 const ll INF = 0x3f3f3f3f; 18 const int eps = 1e-8; 19 const int maxn = 4e5 + 5; 20 inline ll read() 21 { 22 ll ans = 0; 23 char ch = getchar(), last = ' '; 24 while(!isdigit(ch)) {last = ch; ch = getchar();} 25 while(isdigit(ch)) 26 { 27 ans = ans * 10 + ch - '0'; ch = getchar(); 28 } 29 if(last == '-') ans = -ans; 30 return ans; 31 } 32 inline void write(ll x) 33 { 34 if(x < 0) x = -x, putchar('-'); 35 if(x >= 10) write(x / 10); 36 putchar(x % 10 + '0'); 37 } 38 39 int n, m, s, t; 40 struct Node 41 { 42 int x, y; ll c; 43 }a[maxn]; 44 45 vector<ll> v[maxn], c[maxn], v2[maxn], c2[maxn]; 46 47 ll dis[2][maxn]; 48 ll num[2][maxn]; 49 bool vis[maxn]; 50 struct Dij //用pair看起来更简洁 51 { 52 int num; ll cost; 53 bool operator < (const Dij& other)const 54 { 55 return cost > other.cost || (cost == other.cost && num > other.num); 56 } 57 }; 58 void dijkstra(int x, int p) 59 { 60 for(int i = 1; i <= n; ++i) dis[p][i] = INF * INF; 61 Mem(vis); 62 priority_queue<Dij> q; 63 q.push((Dij){x, 0}); 64 dis[p][x] = 0; num[p][x] = 1; 65 while(!q.empty()) 66 { 67 Dij node = q.top(); q.pop(); 68 int now = node.num; 69 if(vis[now]) continue; 70 vis[now] = 1; 71 for(int i = 0; i < (int)v[now].size(); ++i) 72 { 73 if(dis[p][now] + c[now][i] < dis[p][v[now][i]]) 74 { 75 dis[p][v[now][i]] = dis[p][now] + c[now][i]; 76 num[p][v[now][i]] = num[p][now]; 77 q.push((Dij){v[now][i], dis[p][v[now][i]]}); 78 } 79 else if(dis[p][now] + c[now][i] == dis[p][v[now][i]]) num[p][v[now][i]] += num[p][now]; 80 } 81 } 82 } 83 84 ll ans[maxn]; 85 86 int main() 87 { 88 freopen("road.in", "r", stdin); 89 freopen("road.out", "w", stdout); 90 n = read(); m = read(); s = read(); t = read(); 91 for(int i = 1; i <= m; ++i) 92 { 93 a[i].x = read(); a[i].y = read(); a[i].c = read(); 94 v[a[i].x].push_back(a[i].y); 95 c[a[i].x].push_back(a[i].c); 96 } 97 dijkstra(s, 0); 98 for(int i = 1; i <= n; ++i) {v[i].clear(); c[i].clear();} 99 for(int i = 1; i <= m; ++i) 100 { 101 v[a[i].y].push_back(a[i].x); 102 c[a[i].y].push_back(a[i].c); 103 } 104 dijkstra(t, 1); 105 for(int i = 1; i <= m; ++i) 106 { 107 if(dis[0][a[i].x] + dis[1][a[i].y] + a[i].c > dis[0][t]) 108 { 109 ll dx = dis[0][a[i].x] + dis[1][a[i].y] + a[i].c - dis[0][t] + 1; 110 if(a[i].c - dx > 0) ans[i] = dx; //别忘了减完后边权还得是正的 111 } 112 else 113 { 114 if(num[0][a[i].x] * num[1][a[i].y] == num[0][t]) ans[i] = -1; 115 else if(a[i].c > 1) ans[i] = 1; //减1后边权要大于0 116 } 117 } 118 for(int i = 1; i <= m; ++i) 119 { 120 if(ans[i] == -1) printf("YES\n"); 121 else if(!ans[i]) printf("NO\n"); 122 else printf("CAN %lld\n", ans[i]); 123 } 124 return 0; 125 }
T3 归程的谜题
【题目描述】
NOIP 结束了,你踏上了归程。在大巴车上,你很无聊,于是你想了一个谜题来娱
乐一下。
若一个二维数组每个元素都是(或) 中的一个,那么这个数组被成为括号二维
数组。从二维数组的左上角(1; 1) 出发,每次向右或向下移动一格,直到走到右下角
(n;m),这样的路径被成为单调路径。自然地,一条单调路径上的所有字符按顺序相连
可以构成一个括号串。
如果一个括号二位数组的每一条单调路径所对应的括号串都是匹配的括号串,那
么这个括号二维数组被称为匹配的括号二维数组。
现在定义两个相同大小的匹配的括号二维数组的一个比较函数(比较数组a 和b)。
假设二维数组中每个元素都有一个优先级。优先级为1 到nm 的整数,且两两不同(设
优先级储存在一个和a; b 同样大小的二维数组c 中)。然后找到所有ai; j , bi; j 的位置。
如果有多个这样的位置,就选择ci; j 最小的位置。如果ai; j =(,则a < b,否则a > b;
若没有这样的位置,则a = b。
有了比较函数,我们可以给一定大小所有匹配的括号二维数组从小到大排序,你希
望找出具有指定大小的第k 个匹配的括号二维数组。
数据保证第k 个匹配的括号二维数组存在。
【输入格式】
从文件puzzle.in 中读入数据。
第一行有三个正整数n;m; k (1 n;m 500; 1 k 1018),分别表示二维数组的行
数、列数、你希望找到的匹配的括号二维数组的序数。
接下来n 行,每行m 个正整数,表示优先级数组(优先级两两不同)。
【输出格式】
输出到文件puzzle.out 中。
输出n 行,每行一个长度为m 的字符串,表示所求得的二维数组。
【样例1 输入】
3 2 2
3 6
1 4
2 5
第6 页共7 页
全国青少年信息学奥林匹克联赛NOIP 第二试归程的谜题(puzzle)
【样例1 输出】
()
)(
()
【样例2】
见选手目录下的puzzle/puzzle2.in 与puzzle/puzzle2.ans。
【子任务】
对于20% 的数据,n;m 10;
对于另20% 的数据,k 10;
对于另20% 的数据,n = 1。
就是CF123C。
题面我刚开始是没懂,考试剩后一个小时的时候我又读了一遍,总算明白了。还是那句话,多读几遍救懂了……
首先得发现这么个性质,看看下面这个图
这两条路线除了经过1和2号格子不同外,其他格子都相同,而又因为这两条路线都是合法的括号序列,所以可以推出,格子1和2的括号是相同的。那么进一步得出结论,每一个对角线上的括号都是相同的,换句话说只要确定了其中一条路径,那么整张图就确定了。
现在问题转化成了求一条第k小的长度为(n +m - 1)的合法括号序列。
首先我们将所有优先级映射到任意一条序列上,并保证序列第 i 位置的优先级是该格子所在对角线上最小的。接着顺着优先级尝试填这个格子括号,然后计算出在一些位置已经确定的括号