#(补笔记)
#坑中第二期集训day11
今天没有什么心情写总结/晕
因为考试又是一如既往地被dalao们碾压了(可能这就是蒟蒻吧)
好吧还是要(很不情愿地)回顾一下的
嗯那就直接上题目吧
T1 巧克力
emmm这题本来还在感到特别费解不能搜索也不会dp
不过在一番很久的思考后发现n很小,好像可以模拟做……
模拟的过程:
1、从第一位开始逐个找一段连续的区间(len≥1)
2、将区间向两个方向拓展,很容易发现若某字母想从第A位移到第B位,交换次数一定为|A-B|
3、储存下向左和向右拓展一定个相同字母所需的交换次数,用fl[i],fr[i]记录向左右拓展i个相同字母所需的交换次数,则:
那就直接上代码咯~
1 #include <iostream> 2 #include <cstdio> 3 4 using namespace std; 5 6 int n, sw, ans; 7 char s[55]; 8 int fl[55], fr[55]; 9 10 int main() 11 { 12 freopen("a.in", "r", stdin); 13 freopen("a.out", "w", stdout); 14 int T; 15 cin >> T; 16 while (T--) 17 { 18 scanf("%d%d", &n, &sw); 19 scanf("%s", s); 20 ans = 1; 21 for (int i = 0; i < n; i++) // 枚举每个区间的左端点 22 { 23 if (i == 0 || s[i - 1] != s[i]) // 确定该点是左端点 24 { 25 int j, len, cntl, cntr, rpos; 26 cntl = 0; 27 for (j = i - 1; j > 0; j--) // 向左枚举 28 { 29 if (fl[cntl] + (i - cntl) - j - 1 > sw) break; 30 if (s[j] == s[i]) 31 { 32 fl[cntl + 1] = fl[cntl] + (i - cntl) - j - 1; 33 cntl++; 34 } 35 } 36 cntr = 0; 37 while (s[i + cntr + 1] == s[i + cntr]) fr[++cntr] = 0; // 找到区间右端点 38 for (j = i + cntr + 1; j < n; j++) // 向右枚举 39 { 40 if (fr[cntr] + j - (i + cntr) - 1 > sw) break; 41 if (s[j] == s[i]) 42 { 43 fr[cntr + 1] = fr[cntr] + j - (i + cntr) - 1; 44 cntr++; 45 } 46 } 47 for (int k = 0; k <= cntl; k++) 48 for (int l = 0; l <= cntr; l++) 49 if (fl[k] + fr[l] <= sw) 50 ans = max(ans, k + l + 1); 51 } 52 } 53 printf("%d\n", ans); 54 } 55 return 0; 56 }
T2 最小逆序对
这题应该是看起来最水的一题啦~
然后我还是错了希望教练原谅我这种没有重新初始化的智障
观察数据范围:
应该可以想到算法的时间复杂度应该低于O(nlogn),而通过树状数组/线段树或归并求逆序对的时间复杂度是O(nlogn),因此应该先进行预处理求出原数组中的逆序对个数,再对每一个数进行处理
对于样例,用cnt[i]表示以第i个数为结尾的逆序对个数,sum表示每一个序列中的逆序对总数
(emmm我是把一个数从后面移到前面的,感觉这样可能会好理解一点?)
可以看出,除了每一次移动的数字x,有且只有比移动的数字大的数字,cnt值会改变,且都是增加1(逆序对{x,1})
因此,在最后一个数是a[i],此时的sum值为:
代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 #define MAXN 5005 6 7 using namespace std; 8 9 int n, ans; 10 int a[MAXN], c[MAXN], f[MAXN]; 11 int sum[MAXN]; 12 13 int lowbit(int x) // 树状数组求逆序对数进行预处理 14 { 15 return x & (-x); 16 } 17 void add(int pos, int x) 18 { 19 while (pos <= n) 20 { 21 c[pos] += x; 22 pos += lowbit(pos); 23 } 24 } 25 long long query(int pos) // 区间修改,单点查询 26 { 27 long long res = 0; 28 while (pos > 0) 29 { 30 res += c[pos]; 31 pos -= lowbit(pos); 32 } 33 return res; 34 } 35 36 int main() 37 { 38 // freopen("b.in", "r", stdin); 39 // freopen("b.out", "w", stdout); 40 while (cin >> n) 41 { 42 memset(c, 0, sizeof(c)); 43 memset(sum, 0, sizeof(sum)); 44 for (int i = 1; i <= n; i++) 45 { 46 scanf("%d", &a[i]); 47 a[i]++; 48 f[i] = query(a[i]); 49 sum[n] += f[i]; 50 add(1, 1); 51 add(a[i], -1); 52 } // 读入时进行预处理,时间复杂度O(nlogn) 53 ans = sum[n]; 54 for (int i = n - 1; i >= 1; i--) // 枚举数组的最后一个数的原下标 55 { 56 sum[i] = sum[i + 1] - (n - a[i + 1]) + a[i + 1] - 1; 57 ans = min(ans, sum[i]); 58 } 59 printf("%d\n", ans); 60 } 61 }
T3 悟空
今天的最后一题~
做到这一题的时候就只剩40分钟了,想不到正解555
然后就打了暴力dfs加上很简陋的剪枝
算法的时空复杂度好像都是O(n!),想着应该凉了
然后数据太水……老师说数据是随机出的……没有想过专门搞一个完全图来卡我……然后就过了
先贴一下蒟蒻的垃圾算法吧
emmm我默认spfa和dfs不用解释了嗷
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <queue> 5 6 #define MAXN 305 7 8 using namespace std; 9 10 int n, m, ans; 11 int start1, end1, start2, end2; 12 int dist1, dist2; 13 14 struct { 15 int v, w, next; 16 } edge[MAXN * MAXN]; 17 int cnt; 18 int head[MAXN]; 19 bool added[MAXN][MAXN]; 20 21 void add_edge(int a, int b, int c) 22 { 23 if (added[a][b]) 24 return; 25 added[a][b] = 1; 26 cnt++; 27 edge[cnt].v = b; 28 edge[cnt].w = c; 29 edge[cnt].next = head[a]; 30 head[a] = cnt; 31 } 32 33 34 int SPFA(int start, int end) // SPFA预处理用于求最短路长度,用于dfs剪枝 35 { 36 int dist[MAXN]; 37 bool vis[MAXN]; 38 queue<int> q; 39 q.push(start); 40 memset(vis, 0, sizeof(vis)); 41 vis[start] = 1; 42 memset(dist, 0x7f, sizeof(dist)); 43 dist[start] = 0; 44 while (!q.empty()) 45 { 46 int now = q.front(); 47 q.pop(); 48 vis[now] = 0; 49 for (int i = head[now]; i; i = edge[i].next) 50 { 51 int v = edge[i].v; 52 if (dist[now] + edge[i].w < dist[v]) 53 { 54 dist[v] = dist[now] + edge[i].w; 55 if (!vis[v]) 56 { 57 q.push(v); 58 vis[v] = 1; 59 } 60 } 61 } 62 } 63 return dist[end]; 64 } // 特别标准的spfa了吧…… 65 66 67 vector<bool> book; 68 vector<vector<bool> > v1, v2; // 储存所有的最短路径,不知道有多少只能开vector咯 69 70 void dfs1(int sum, int now) 71 { 72 if (sum > dist1) // 很烂的剪枝…… 73 return; 74 if (now == end1) // 直接把book数组全存进去 75 v1.push_back(book); 76 else { 77 for (int i = head[now]; i; i = edge[i].next) 78 { 79 int v = edge[i].v; 80 if (book[v]) continue; 81 book[v] = 1; 82 dfs1(sum + edge[i].w, v); 83 book[v] = 0; 84 } 85 } 86 } 87 void dfs2(int sum, int now) // 另一条最短路 88 { 89 if (sum > dist2) 90 return; 91 if (now == end2) 92 v2.push_back(book); 93 else { 94 for (int i = head[now]; i; i = edge[i].next) 95 { 96 int v = edge[i].v; 97 if (book[v]) continue; 98 book[v] = 1; 99 dfs2(sum + edge[i].w, v); 100 book[v] = 0; 101 } 102 } 103 } 104 105 int main() 106 { 107 while (true) 108 { 109 scanf("%d%d", &n, &m); 110 if (n == 0 && m == 0) // 数据结束标志 111 return 0; 112 cnt = 0; // 注意重新初始化!!! 113 ans = 0; 114 memset(head, 0, sizeof(head)); 115 memset(added, 0, sizeof(added)); 116 for (int i = 1; i <= m; i++) 117 { 118 int a, b, c; 119 scanf("%d%d%d", &a, &b, &c); 120 add_edge(a, b, c); 121 add_edge(b, a, c); // 双向边 122 } 123 scanf("%d%d%d%d", &start1, &end1, &start2, &end2); 124 dist1 = SPFA(start1, end1); 125 dist2 = SPFA(start2, end2); 126 v1.clear(); 127 v2.clear(); 128 book.clear(); // 初始化#2 129 for (int i = 0; i <= n; i++) book.push_back(0); 130 book[start1] = 1; 131 dfs1(0, start1); 132 book.clear(); 133 for (int i = 0; i <= n; i++) book.push_back(0); 134 book[start2] = 1; 135 dfs2(0, start2); 136 for (int i = 0; i < v1.size(); i++) 137 for (int j = 0; j < v2.size(); j++) //直接枚举所有最短路…… 138 { 139 int tot = 0; 140 for (int k = 1; k <= n; k++) 141 if (v1[i][k] && v2[j][k]) 142 tot++; 143 ans = max(tot, ans); 144 } 145 cout << ans << endl; 146 } 147 }
咳不用吐槽我的暴力了
我们还是来寻找正解吧2333
根据最短路径的定义:
可以发现,如果一条最短路径上经过两点a,b,则一定会经过他们之间的最短路{mindist(a,b)}
因此,如果悟空和唐僧的最短路同时有两个点a,b,则{mindist(a,b)}中的某一条,经过的点数最多的最短路,一定在他们所走的路上,只有这样才能保证路径最短的情况时,公共点最多
即:在两人选择的路径上的顶点按经过顺序构成的区间中,有不多于一段公共的连续区间(相同点的顺序可能相反,即有可能悟空经过1->2->3->5->4,而唐僧经过6->5->3->2->7,则此时{2,3,5}为公共点集
那么这题就可以很好地转换为一个区间dp,通过floyd的方式求解
用dis[i][j]表示i->j的最短路,dp[i][j]表示最短路,则:
那就放一下老师的代码吧:
1 #include <cstdio> 2 #include <cstring> 3 #define max(x,y) (x > y ? x : y) 4 #define MAX 302 5 #define INF 1000000000 6 using namespace std; 7 8 int dis[MAX][MAX],dp[MAX][MAX]; 9 int n,m; 10 11 void floyd(){ 12 for(int u=1;u<=n;u++){ 13 for(int i=1;i<=n;i++){ 14 for(int j=1;j<=n;j++){ 15 if(dis[i][j]>dis[i][u]+dis[u][j]){ // 可以更新最短路,必须要更新dp 16 dis[i][j]=dis[i][u]+dis[u][j]; 17 dp[i][j]=dp[i][u]+dp[u][j]; 18 }else if(dis[i][j]==dis[i][u]+dis[u][j]){ // 路径长度相同,取dp值较大的 19 dp[i][j]=max(dp[i][u]+dp[u][j],dp[i][j]); 20 } 21 } 22 } 23 } 24 } 25 26 int main() 27 { 28 int a,b,l; 29 int s1,e1,s2,e2; 30 int ans; 31 while(scanf("%d %d",&n,&m)==2 && (n+m)){ 32 for(int i=1;i<=n;i++){ 33 for(int j=1;j<=n;j++){ 34 dis[i][j]= i==j ? 0 : INF; 35 dp[i][j]=0; 36 } 37 } 38 for(int i=0;i<m;i++){ 39 scanf("%d %d %d",&a,&b,&l); 40 if(dis[a][b]>l){ 41 dis[a][b]=dis[b][a]=l; 42 dp[a][b]=dp[b][a]=1; 43 } 44 } 45 46 scanf("%d %d %d %d",&s1,&e1,&s2,&e2); 47 floyd(); 48 ans=-1; 49 for(int i=1;i<=n;i++){ 50 for(int j=1;j<=n;j++){ 51 if(dp[i][j]>ans && (dis[s1][e1] == dis[s1][i]+dis[i][j]+dis[j][e1] // 检查i->j是否在两人的最短路上 52 ||dis[s1][e1] == dis[s1][j]+dis[j][i]+dis[i][e1]) 53 && (dis[s2][e2] == dis[s2][i]+dis[i][j]+dis[j][e2] 54 ||dis[s2][e2] == dis[s2][j]+dis[j][i]+dis[i][e2]) ){ 55 ans=dp[i][j]; 56 } 57 } 58 } 59 printf("%d\n",ans+1); 60 } 61 return 0; 62 }
(前排吐槽一下老师的码风,484只有我一个人喜欢疯狂空格换行……)
今日份的总结大概就是这样啦~
还是暴露了自己相当多的不足吧
加油!