关于第二次测试の总结

The Second Test


死亡回放 比赛时间线

  • 6:38 打开题面
  • 发现前 4 题原题我都押对了! qwq
  • 光速开始写 T1 第二饭堂
  • 约 6:50 写完了 T1 第二饭堂 第一版代码但是样例没过
  • 约 7:00 在紧张中往 T1 第二饭堂 第一版代码中多加了个转移数组,然后过了样例
  • 然后开始写 T2 飞行路线 ,因为这是我唯一一道完全是自己想、自己写过的蓝题所以印象特别深刻
  • 约 7:15 一遍过掉样例,但总感觉原题目是双向边但给的题面没说清,然后就去问了老师
  • 然后就先去写 T3 最大半连通子图
  • 约 7:30 写掉第一版代码,发现缩点缩炸了
  • 约 7:35 改掉了缩点BUG,然后样例输出了 3 2
  • 之前 wsr 输出这个样例之后就再也没有调出来
  • 所以我有点崩溃 qwq
  • 但是在 7:40 的时候我调对啦!最后拓扑求答案的时候有个转移忘了写力! qwq
  • 然后开始写 T4 种树
  • 约 8:00 在我零碎的记忆中终于把差分约束的边建对来了,样例也就过了
  • 然后就得到了 T2 飞行路线 的路线都是双向边
  • 剩下一个小时都在打 T6 JOIOJI20\% 的暴力(奇迹般地拿了 40\% qwq
  • 全场最错误的选择:没有去骗 T5 染色 的分!!!

死因分析 错误点分析

  • 没有去骗 T5 的分! 后面的时间其实挺充足的但是都在摸鱼 qwq
  • 没有去推 T6 的式子! cym 推了式子之后就把 T6 给过了 awa

死亡证明 成绩

遗书 各题错误思路 & 正解

T1 第二饭堂

由于一饭班长表示各种鸭梨,美丽的纪中决定历史性地启用第二饭堂。

而部分领导觉得,二饭依山傍水,环境优美,未免有不和谐的事情(你懂的)发生,决定到二饭巡视同学们用餐时的就座情况。

为了应付这一情况,同学们决定联合起来“布阵”。

方便起见,同学们已经把座位情况抽象成一个长度为n的仅含数字及字母的字符串,他们想请你帮忙算算这个字符串的和谐程度。

已知一个字符串被称为k-回文串的充要条件是它自身是回文串,并且它长为⌊n/2⌋(下取整)的前缀和后缀是(k-1)-回文串。根据定义,任意字符串

(包括空串)都是0-回文串。

一个字符串的回文度数就是这个字符串的k的最大值。

而对于一个给定的字符串,它的和谐程度就是其所有前缀的回文度数之和。

你的任务就是算出这个和谐程度具体是多少。

正解

  • 分别记录两个字符串哈希值,一个记录正着的,一个记录反着的

  • 1n 扫一遍,如果正反两个哈希值相等则可以更新答案

  • 当天的笔记:day 9 拓扑排序和哈希笔记

    #include<bits/stdc++.h>
    #define ll unsigned long long
    using namespace std;
    const int maxn = 5000005,BASE = 27;
    char s[maxn]; int n;
    ll p = 0,t = 0,e = 1; long long f[maxn],g[maxn];
    int main() {
    	scanf("%s",s + 1); n = strlen(s + 1);
    	for (int i = 1;i <= n;i ++) {
    		p = p * BASE + s[i], t = t + s[i] * e, e *= BASE;
    		if (p == t) f[i] = f[i >> 1] + 1; g[i] = g[i - 1] + f[i];
    	}
    	printf("%lld",g[n]);
    	return 0;
    }
    

T2 JLOI2011 飞行路线

爱丽丝和鲍勃现在乘飞机旅行,他们选择了一个相对便宜的航空公司。该航空公司在 n 个城市提供服务。这些城市标记为 0n-1 ,并且有m种类型的路线。每条路线连接两个城市,路线有一定的价格。爱丽丝和鲍勃现在正沿着这条路从一个城市前往另一个城市,并且可以在途中进行过境。航空公司还为他们的旅行提供折扣,他们可以乘坐 k 条免费路线。那么 AliceBob 的旅行的最低费用是多少?

正解

  • 注意:此题的路线为双向边,建议去洛谷上看

  • 这题在洛谷上是个蓝题但是我徒手切爆力 qwq

  • dp[i][j] 表示走到 i 城市,消耗 j 条免费路线的最小代价

  • 初始化 dp[i][0] = dis[i] ,其中 dis[i] 表示从起点到 i 城市的最短路

  • 转移的时候枚举 k ,假设当前点为 v ,从 u 点进行转移, (u,v) 之间的连边的代价为 w(u\to v)

  • 转移方程为:

    dp[v][k] = \min(dp[u][k - 1],dp[u][k] + w(u\to v))
  • 初始化和转移的过程都可以用最短路进行维护

  • 最后答案从 dp[t][0\to k] 中取最小值,其中 t 表示终点

  • 当天的笔记:day10 最短路

    #include<bits/stdc++.h>
    #define type pair<int,int>
    #define mk make_pair
    using namespace std;
    const int maxn = 100005;
    int n,m,k,s,t,f[maxn][15]; vector<type> mp[maxn]; bool vis[maxn];
    void addEdge(int u,int v,int w) { mp[u].push_back(mk(v,w)); }
    void Init() {
    	priority_queue<type,vector<type>,greater<type> > q;
    	q.push(mk(0,s)); memset(f,0x3f,sizeof(f)); f[s][0] = 0;
    	while (!q.empty()) {
    		type top = q.top(); q.pop();	
    		int u = top.second; vis[u] = true;
    		for (auto v : mp[u])
    			if (f[v.first][0] > f[u][0] + v.second) {
    				f[v.first][0] = f[u][0] + v.second;
    				if (!vis[v.first]) q.push(mk(f[v.first][0],v.first));
    			}
    	}
    }
    void Dijkstra() {
    	priority_queue<type,vector<type>,greater<type> > q;
    	q.push(mk(0,s)); memset(vis,false,sizeof(vis));
    	while (!q.empty()) {
    		type top = q.top(); q.pop();	
    		int u = top.second; vis[u] = true;
    		for (auto v : mp[u]) 
    			for (int p = 1;p <= k;p ++)
    				if (f[v.first][p] > min(f[u][p - 1],f[u][p] + v.second)) {
    					f[v.first][p] = min(f[u][p - 1],f[u][p] + v.second);
    					if (!vis[v.first]) q.push(mk(f[v.first][p],v.first));
    				}
    		
    	}
    }
    int main() {
    	scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
    	for (int i = 1,u,v,w;i <=  m;i ++) {
    		scanf("%d%d%d",&u,&v,&w);
    		addEdge(u,v,w); addEdge(v,u,w);
    	}
    	Init(); Dijkstra(); int ans = 0x3f3f3f3f;
    	for (int i = 0;i <= k;i ++) ans = min(ans,f[t][k]);
    	printf("%d",ans);
    	return 0;
    }
    

T3 最大半连通子图

正解

  • 先把所有的强连通分量缩成一个点并记录大小,因为一个强连通分量中的点要么都在最大半连通子图里,要么都不在(自己体会一下 qwq

  • 然后就可以在拓扑的时候 dp 计算答案

  • f,g 分别表示最大半连通子图的大小,和最大半连通子图的数量

  • 初始化 f[i] = siz[i],g[i] = 1 其中 siz[i] 表示编号为 i 的强连通分量的大小

  • 假设当前点为 v ,从 u 点进行转移

    1. 如果 f[u]+siz[v]>f[v] ,则

      f[v]=f[u]+siz[v],g[v]=g[u]
    2. 如果 f[u] + siz[v] = f[v],则

      g[v] = g[v] + g[u]
  • 最后答案从每个强连通分量里去求

  • 当天的笔记:day 11 强连通等

    #include<bits/stdc++.h>
    #define mk make_pair
    using namespace std;
    const int maxn = 1e5 + 5,maxm = 1e6 + 5;
    int n,m,P,dfn[maxn],low[maxn],tot,top,d[maxn],st[maxm],b[maxn],siz[maxn],cnt; bool vis[maxn]; vector<int> mp[maxn];
    vector<int> nmp[maxn]; vector<pair<int,int> > e; int f[maxn],g[maxn];
    void addEdge(int u,int v) { mp[u].push_back(v); }
    void addnewEdge(int u,int v) { nmp[u].push_back(v); d[v] ++; }
    void Tarjan(int u) {
    	dfn[u] = low[u] = ++ tot, st[++ top] = u, vis[u] = true;
    	for (auto v : mp[u]) 
    		if (!dfn[v]) {
    			Tarjan(v);
    			low[u] = min(low[u],low[v]);
    		} else if (vis[v]) low[u] = min(low[u],dfn[v]);
    	if (dfn[u] == low[u]) {
    		siz[++ cnt] = 1, b[u] = cnt;
    		while (st[top] != u) siz[cnt] ++, b[st[top]] = cnt, vis[st[top]] = false, top --;
    		vis[u] = false, top --;
    	}
    }
    void Solve() {
    	queue<int> q;
    	for (int i = 1;i <= cnt;i ++) {
    		f[i] = siz[i], g[i] = 1;
    		if (d[i] == 0) q.push(i);
    	}
    	while (!q.empty()) {
    		int u = q.front(); q.pop();
    		for (auto v : nmp[u]) {
    			d[v] --;
    			if (f[v] < f[u] + siz[v]) g[v] = g[u], f[v] = f[u] + siz[v];
    			else if (f[v] == f[u] + siz[v]) g[v] = (g[u] + g[v]) % P;
    			if (d[v] == 0) q.push(v);
    		}
    	}
    }
    void build() {
    	for (int u = 1;u <= n;u ++)
    		for (auto v : mp[u])
    			if (b[u] != b[v])
    				e.push_back(mk(b[u],b[v]));
    	sort(e.begin(),e.end()); int i = 0;
    	for (auto E : e) {
    		int u = E.first, v = E.second;
    		if (i > 0 && u == e[i - 1].first && v == e[i - 1].second) {
    			i ++; continue;
    		}
    		addnewEdge(u,v); i ++;
    	}
    }
    int main() {
    	scanf("%d%d%d",&n,&m,&P);
    	for (int i = 1,u,v;i <= m;i ++) {
    		scanf("%d%d",&u,&v);
    		addEdge(u,v);
    	}
    	for (int i = 1;i <= n;i ++)
    		if (!dfn[i]) Tarjan(i);
    	build(); Solve();
    	int pos = 0,ans = 0; f[0] = -1;
    	for (int i = 1;i <= cnt;i ++)
    		if (f[i] > f[pos]) pos = i, ans = g[i];
    		else if (f[i] == f[pos]) ans = (ans + g[i]) % P;
    	printf("%d\n%d",f[pos],ans);
    	return 0;
    }
    

T4 种树

为了绿化乡村,H村积极响应号召,开始种树了。
H村里有 n 幢房屋,这些屋子的排列顺序很有特点,在一条直线上。于是方便起见,我们给它们标上 1\to n 。树就种在房子前面的空地上。
同时,村民们向村长提出了 m 个意见,每个意见都是按如下格式:希望第 l_i 个房子到第 r_i 个房子的房前至少有 c_i 棵树。
因为每个房屋前的空地面积有限,所以每个房屋前最多只能种 k_i 棵树。
村长希望在满足村民全部要求的同时,种最少的树以节约资金。请你帮助村长。

正解

  • 对前缀和数组建立差分约束系统

  • 总共有三个约束:(设前缀和数组为 sum

    1. sum[r]-sum[l-1]\ge c
    2. sum[i]-sum[i-1]\ge0
    3. sum[i-1]-sum[i]\ge -k
  • 把边建完之后跑最长路即可

  • 当天的笔记:day10 最短路

    #include<bits/stdc++.h>
    #define mk make_pair
    #define type pair<int,int>
    using namespace std;
    const int maxn = 500005;
    int n,m,tree[maxn]; vector<type> mp[maxn];
    bool vis[maxn]; int dis[maxn];
    void addEdge(int u,int v,int w) {
    	mp[u].push_back(mk(v,w));
    }
    void SPFA() {
    	queue<int> q; q.push(1); memset(dis,-0x3f,sizeof(dis)); dis[1] = 0;
    	while (!q.empty()) {
    		int u = q.front(); q.pop(); vis[u] = false;
    		for (auto v : mp[u])
    			if (dis[v.first] < dis[u] + v.second) {
    				dis[v.first] = dis[u] + v.second;
    				if (!vis[v.first]) {
    					vis[v.first] = true;
    					q.push(v.first);
    				}
    			}
    	}
    }
    int main() {
    	scanf("%d%d",&n,&m);
    	for (int i = 1,x;i <= n;i ++) {
    		scanf("%d",&x);
    		tree[i] = tree[i - 1] + x;
    		addEdge(i + 1, i,-x); addEdge(i,i + 1,0);
    	}
    	addEdge(0,1,0);
    	for (int i = 1,u,v,k;i <= m;i ++) {
    		scanf("%d%d%d",&u,&v,&k);
    		addEdge(u,v + 1,k);
    	}
    	SPFA();
    	printf("%d",dis[n + 1]);
    	return 0;
    }
    // OHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
    

T5 区间染色

假设你现在有一块白板,想要仿照着一幅简单的油画进行创作。这个油画比较特殊,它将画板分成了 n 个区间,每个区间都画了不同的颜色。使用笔刷一次可以将任意长度的区间刷成同样的颜色。后刷的颜色会覆盖先刷的颜色。现在你想知道最少要多少次可以画出这幅简单的油画。

错误思路

  • 完全不知道暴力怎么打就直接

    不可以,总司令

  • qwq

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 2e5 + 5;
    int n,ans = 1,a[maxn]; char s[maxn];
    int main() {
    	scanf("%s",s + 1);
    	printf("%d",ans);
    	return 0;
    }
    

正解

  • 这个题在洛谷上我以前竟然 AC 过!我是 fw qwq

  • 区间 dp ,设 f[i][j] 表示把 [i,j] 区间刷成一种颜色的最小代价

  • 初始化 f[i][j] =+\infty,f[i][i] = 1

  • 假设当前枚举的区间为 [i,j] ,区间内一点为 k ,从小区间往大区间计算,则转译为:

    f[i][j] = \min(f[i][k]+f[k + 1][j],f[i][j])
  • 如果 s[i]s[j] 相等( s 为原字符串),则

    f[i][j] = \min(f[i][j - 1],f[i + 1][j])
  • 答案即为 f[1][n] ,其中 ns 的长度

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 505; const int inf = 0x3f3f3f3f;
    char s[maxn]; int f[maxn][maxn],len;
    int main() {
        scanf("%s",s + 1); len = strlen(s + 1);
        memset(f,0x3f,sizeof(f));
        for (int i = 1;i <= len;i ++) f[i][i] = 1;
        for (int l = 1;l <= len;l ++)
            for (int i = 1,j = l + 1;j <= len;i ++,j ++) {
                if (i == j) continue;
                for (int k = i;k < j;k ++) f[i][j] = min(f[i][k] + f[k + 1][j],f[i][j]);
                if (s[i] == s[j]) f[i][j] = min(f[i][j - 1],f[i + 1][j]);
            }
        printf("%d",f[1][len]);
        return 0;
    }
    

T6 JOIOJI

JOIOJI 桑是 JOI 君的叔叔。JOIOJI 这个名字是由 J, O , I 三种字母各两个构成的。

最近,JOIOJI 桑喜当爹。JOIOJI 桑想让自己孩子的名字和自己一样由 J , O, I 三种字母构成,并且想让 J , O, I 三个字母的出现次数恰好相同。

JOIOJI 桑家有一份祖传的卷轴,上面写着一首长诗 S ,长度为 N ,由 J , O, I 三种字母组成。JOIOJI 桑想用诗中最长的满足要求的连续子串作为孩子的名字。

现在 JOIOJI 桑将这首长诗交给了你,请你求出诗中最长的、包含同样数目的 J , O , I 三种字母的连续子串。

错误思路

  • 瞅着这 20\% 的暴力分我很馋啊!

  • 然后就打了个暴力,奇迹般地拿了 40\% qwq

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 2e5 + 5;
    int n,ans,a[maxn]; char s[maxn];
    int main() {
    	scanf("%d",&n); scanf("%s",s + 1);
    	for (int len = 1;len <= n;len ++) {
    		a['J'] = a['O'] = a['I'] = 0;
    		for (int i = 1;i <= len;i ++)
    			a[s[i]] ++;
    		for (int i = 1,j = i + len - 1;j <= n;i ++,j ++) {
    			if (a['J'] == a['O'] && a['O'] == a['I']) {
    				ans = len; break;
    			}
    			a[s[i]] --, a[s[j + 1]] ++;
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }
    

正解

  • cym 神犇的思路:

    sJ[j]-sJ[i-1]=sO[j]-sO[i-1]=sI[j]-sI[i-1]

    sJ[j]-sJ[i-1]

    sJ[j]-sO[j]-(sI[i-1]-sO[i-1])=sI[j]-sJ[j]-(sI[i-1]-sJ[i-1]),

    x[i]=sO[i]-sJ[i],y[i]=sI[i]-sJ[i]

    x[i-1]=x[j]y[i-1]=y[j]

    如果当前字符是“J”,s1[i]=s1[i-1]+1s2[i],s3[i] 不变 则 x[i]=x[i-1]-1,y[i]=y[i-1]-1;
    如果当前字符是“O”,即 s2[i]=s2[i-1]+1s1[i],s3[i] 不变 则$x[i]=x[i-1]+1,y[i]=y[i-1] $;
    如果当前字符是“I”,即 s3[i]=s3[i-1]+1s1[i],s2[i] 不变 则$x[i]=x[i-1],y[i]=y[i-1]+1$;
    令一个点 i(x[i],y[i]) 点权为 i 相当于查询最大的相同位置的差 快排一下扫一遍

  • 自己对着代码意会一下吧 qwq

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 2e5 + 5;
    int n, j[maxn], o[maxn], I[maxn], ans; char c;
    map<int, map<int, int> > joi;
    int main() {
        scanf("%d", &n); getchar();
        for (int i = 1; i <= n; i ++) {
            c = getchar();
            if (c == 'J') j[i] = 1;
            if (c == 'O') o[i] = 1;
            if (c == 'I') I[i] = 1;
        }
        for (int i = 1; i <= n; i ++) 
            j[i] = j[i - 1] + j[i], o[i] = o[i - 1] + o[i], I[i] = I[i - 1] + I[i];
        for (int i = 1; i <= n; i ++) 
            if (!joi[j[i] - o[i]][o[i] - I[i]]) joi[j[i] - o[i]][o[i] - I[i]] = i;
            else ans = max(ans, i - joi[j[i] - o[i]][o[i] - I[i]]);
        for (int i = 1; i <= n; i ++) 
            if (j[i] == o[i] && o[i] == I[i])
                ans = max(ans, i);
        printf("%d", ans);
        return 0;
    }
    

葬礼 总结

  • 至少比上次好 qwq
  • 然后会做的题分都拿到了

投胎转世 目标

  • 如果下次还是原题的话那在保持原题都能过的情况下多过一道题

各位dalao给蒟蒻一个赞呗,写这个总结真的不容易 qwq

4 个赞

tjl

5 个赞

666

4 个赞

对啊,很对啊
我的半联通子图哪里挂了!(恼)

3 个赞

蒟蒻(X)
巨佬(√)

3 个赞

3 个赞

az

3 个赞