一题之计在于补呐
补题很快乐的一点就是看不懂且听不明白但是写注释中理解到了。
果然学会一道题最简单的方式是给一个纯萌新讲。
说在前面
今天%你赛手感非常好,可能是换了一个位置的原因(玄
首先T1没有读错题是一个比较大的进步,因为DAY1和2都是因为差不多这个原因寄掉了,读对题目果然可以A掉还不错。T2做题的时候没有想到正解就很逆天,赛后重构发现第一遍敲代码时有一部分思想重合了但没发现可以这么做(因为zj是直接找到 pos
的位置,但我的代码是按照正解又模拟的(屑
T3赛时一眼合并果子弱化版的强化版(指不需要桶排但一次可以合并 m
个。赛后发现要考虑直接合合不了的情况。T4搓了一个部分分就跑路了,赛后又发现加个判断可以多拿三十(今天还是很屑。
很屑的是有一道题直接输出YES有70pts,总司令又又又可以了。
所以挂掉(指可以拿到的但没有拿到)居然达到了惊人的90分。数据加强谢谢。
最后得分:100+20+10+10 = 140,个人觉得属于不怎么好但比前几天好的情况了。
但今天没有彩蛋也没有1145141919810就很不好
T1-IP地址(ip)
赛时敲出正解。
模拟即可。当时 map
忘掉怎么写了,所以手搓结构体,本来觉得会爆掉时间复杂度,但一看范围 \(10^5\) 快乐A。
笑点解析:老师说要 scanf
,写了之后好像有同学挂掉了。
锐评:不如手敲读优。
歪解:
#include <iostream>
#include <map>
using namespace std;
struct Node {
string s,s1;
}a[1005];
void read(long long &x){
int f = 1;
x = 0;
char s = getchar();
while(s < '0' || s > '9') {
if(s == '-') f = -1;
s = getchar();
}
while(s >= '0' && s <= '9') {
x = x * 10 + s - '0';
s=getchar();
}
x *= f;
}
int main() {
// freopen("ip.in","r",stdin);
// freopen("ip.out","w",stdout);
cin.tie();
cout.tie();
long long n;
read(n);
for(int i = 1; i <= n; i ++) {
cin >> a[i].s >> a[i].s1;
}
int q;
cin >> q;
for(int i = 1; i <= q; i ++) {
string s2;
cin >> s2;
for(int j = 1; j <= n; j ++) {
if(a[j].s1 == s2) cout << a[j].s << endl;
}
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
正解改成 map
即可。
T2 - 是否同构(same)
赛时敲到20。
TLE只能说明我的算法不够优啊,枚举k本来就是一个不怎么高明的方法,只能说当时指向正解的指针可能歪掉了。
放一下代码:
#include <iostream>
#define int long long
using namespace std;
const int MAXN = 1e6 + 10;
int a[MAXN], b[MAXN];
void read(long long &x){
int f = 1;
x = 0;
char s = getchar();
while(s < '0' || s > '9') {
if(s == '-') f = -1;
s = getchar();
}
while(s >= '0' && s <= '9') {
x = x * 10 + s - '0';
s=getchar();
}
x *= f;
}
signed main() {
// freopen("same.in","r",stdin);
// freopen("same.out","w",stdout);
int t;
cin >> t;
while(t --) {
int n;
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++) cin >> b[i];
int flag2 = 0;
for(int i = 1; i <= n / 2; i ++) { // 枚举k
int flag = 0;
for(int j = 1; j <= n - i; j ++) {
swap(a[j], a[n - i + 1]);
}
for(int i = 1; i <= n; i ++) {
if(a[i] != b[i]) {
flag = 1;
break;
}
}
if(flag == 0) {
cout << "No\n";
flag2 = 1;
break;
}
}
if(flag2 == 0) cout << "Yes\n";
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
//3
//3
//1 2 3
//3 2 1
//4
//3 1 2 4
//4 3 1 2
//5
//2 3 1 4 5
//5 3 1 4 2
正解是很好想的办法,在 \(a\) 数组里找 \(b_{1}\) 出现的位置,然后记录下来,也就是 pos
。
接着安照题意 swap
即可。
正解:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
int a[MAXN], b[MAXN], n;
bool check() {
for(int i = 1; i <= n; i ++) if(a[i] != b[i]) return 0;
return 1;
}
int main() {
int t;
cin >> t;
while(t --) {
cin >> n;
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i ++) scanf("%d", &b[i]);
int flag = 0;
if(check()) {
printf("Yes\n");
flag = 1;
continue;
}
int pos = 0;
for(int i = n - (n / 2) + 1; i <= n; i ++) {
if(a[i] == b[1]) {
pos = i;
break;
}
}
for(int i = pos; i <= n; i ++) {
swap(a[i], a[i - pos + 1]);
}
if(check()) printf("Yes\n");
else printf("No\n");
}
return 0;
}
T3-箱子(box)
赛时敲掉样例。
但样例过水以至于没有发现需要补0,补上0之后分数果真补0了。
优先队列自动排序不用管,每次选最小(也就是贪心就好),因为第一次选的肯定是最多次的,所以越小越好,这就是这道题的思路。
很好的是贪心是可以的,我们可以试图证明一下。
贪心思路:每次选最小的装进去。
下面思路来自于this:
证明不断取最小的两堆合并成较大的一堆是最优的。
①最优方案可以表示成一个二叉树。
\(总代价 \sum_{i=1}^{n} a_i × depth_i∑
i=1
\)其中 \(depth\) 是深度,也就是这堆果子在整个过程中被有效合并了几次。
注意:\(a_i\) 都是叶子结点。非叶子结点都是合并后的产物。
②最小的两堆一定在最优方案树的最深层。
这个用反证法。假设有一个最优方案树,其最深层中没有最小的两堆。那么把最小的堆与最深层的某堆互换位置形成新方案,保证新方案存在而且新方案的代价小于原方案。
注意:最深层总是一组(一组有两个)或多组叶子节点,来表示它们被直接合并。
③同层叶子节点互换,对总代价无影响。
根据①的 \(\sum\) 得。可见,最小的两堆,如果在最优方案树最深层中不是即将合并的一组,那么可以无偿换为一组。
④根据上两步,我们已经明确:最优方案需要直接合并当前最小的两堆。现在我们就进行这个操作。事实是:现在只剩 \(n-1\) 堆了。
我们只知道刚才进行了一个绝对不错的操作,而不记得操作之前是什么样的。我们只想对现在剩下的几堆陌生的果子进行最好的操作。忽略之前的树,于是回到①了。
注意:这并不意味着之前一步的操作使日后的代价和非常优秀。
显然的,延伸到取 \(m\) 个也是同样的思路。
大佬思路真的很清晰啊啊啊。(土拨鼠嚎叫
所以说是 https://www.luogu.com.cn/problem/P1090 的加强版也没有什么问题对不对。
正解思路:
观察到每次把 \(m\) 个合并成 \(1\) 个相当于减去了 \(m - 1\) 个,然后我们可以分两种情况讨论:
-
\((n-1)\space mod\space (m - 1) = 0\) 这种情况我们每次取 个合并最终可以刚好合并成一个,然后我们使用一个小根堆,每次取最小的 \(m\) 个合并即可。
-
\((n-1)\space mod\space (m - 1) ≠ 0\) 这种情况一定会发生一次当前剩余数量不够 \(m\) 个的情况,那么我们可以补足若干个 \(0\),使得满足\((n-1)\space mod\space (m - 1) = 0\),然后我们按照上述方法做即可,此方法等价于在第一次合并的时候少选几个箱子,以便让后面的合并可以每次都取 \(m\) 个。
扔下正解:
#include <bits/stdc++.h>
using namespace std;
long long n,m,x,ans;
priority_queue<long long,vector<long long>,greater<long long> >q;
void read(long long &x){
int f = 1;
x = 0;
char s = getchar();
while(s < '0' || s > '9') {
if(s == '-') f = -1;
s = getchar();
}
while(s >= '0' && s <= '9') {
x = x * 10 + s - '0';
s=getchar();
}
x *= f;
}
int main(){
// freopen("box.in","r",stdin);
// freopen("box.out","w",stdout);
read(n);
read(m);
for(int i = 1; i <= n; i ++) {
read(x);
q.push(x);
}
if((n - 1) % (m - 1) > 0) {
int cnt = m - 1 - (n - 1) % (m - 1);
while(cnt --) q.push(0);
}
while(!q.size()){
long long cnt = 0;
bool f = 0;
for(int i = 1; i <= m; i ++) {
if(!q.size()) {
cnt += q.top();
// cout << q.top() << " ";
q.pop();
} else {
f = 1;
break;
}
}
if(f) break;
q.push(cnt);
ans += cnt;
}
printf("%lld",ans);
// fclose(stdin);
// fclose(stdout);
return 0;
}
T4-社恐的聚会(party)
行了这道题不会就算了吧。
挣扎一下,眼前一亮,出题人非常良心的给了什么?!是部分分啊,可是不用动脑子就能拿到的10分啊!
其实再动一下脑子,发现加上一个判断可以多拿30pts。
最后我们来分析一下正解(因为作者真的蒟蒻不会正解)
我们把所有不互相认识的人之间连一条无向边,然后我们可以对于所有连通块判断当前连通块是否是二分图(使用黑白染色法)。
要素过多了哈。
- 不互相认识的人:指的是我们建图之后两个人中间不是双向的,也就是A认识B但B不认识A,当然也就是我们正解代码中的:
if(!g[i][j] || !g[j][i])
,同样的,他等效于!(g[i][j]&&g[j][i])
。
如果不是二分图,则直接输出 NO ,否则我们可以记录一下每个连通块中黑点的数量和白点的数量(代表在第一张桌子还是第二张桌子)。注意,黑点和白点本质没有任何区别,我们每个连通块都可以黑白互换。然后我们求得每个连通块的黑点和白点数量后,我们可以做一遍判定性背包dp来求解答案。
-
如果不是二分图,则直接输出 NO:显然的偏分技巧。
-
我们每个连通块都可以黑白互换:本质上只是一个标记的方法,用什么颜色都可以的喵。
-
我们可以做一遍判定性背包dp来求解答案:这也是这道题最好的地方,因为
我看不懂dp本质上是有决策的递归(似乎不是来着,反正不是搜索就是dp)。明显的,这道题爆搜会炸掉
具体的,设 \(dp_{i,j,0}\) 表示前 \(i\) 个连通块,是否能塞入 \(j\) 个点到第一张桌子(白点)。设 \(dp_{i,j,1}\) 表示前 \(i\) 个连通块,是否能塞入 \(j\) 个点到第二张桌子(黑点)。
- 注意每个连通块的黑点和白点是可以互换的。
这个解释非常详细的说明了思路,同时也没有想过我们这种xxs听不懂的问题,不过没关系,代码里的注释比较详细。
dp在老师讲完之后倒是比较清晰了。昨天时候讲的图今天完全不会用,果真是蒟蒻了。
本题知识点:二分图,链式前向星,存在性dp,dfs……
总之是一道超纲的题目。放到明天骗分专场倒是不错。
正解:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 525;
int head[MAXN], nxt[MAXN * MAXN], to[MAXN * MAXN],cnt;
void add(int u, int v) {
nxt[++ cnt] = head[u];
to[cnt] = v;
head[u] = cnt;
} // 构造一个链式前向星存一下
// 整篇代码唯一简单易懂的地方(真的简单易懂哈
int n, g[MAXN][MAXN];
bool vis[MAXN];
int color[MAXN], sz[MAXN][2], idx; // 翻译一下,size是长度的喵
// idx是目标,Coler是可爱的颜色
// 这就关系到这道题第二个地方——黑白染色(
// 因为看不懂所以可爱到了
bool dfs(int u, int c) { // 是一个dfs,看函数名字就能看出来
// 主要作用是判断是否为二分图
// 因为只有两张桌子对不对?所以二分图(胡说
vis[u] = true;
color[u] = c;
sz[idx][c] ++;
for(int i = head[u]; i ; i = nxt[i]) {
int v = to[i];
if(vis[v]) {
if(color[u] == color[v]) return 0; // 判断为假
else {
if(!dfs(v, c ^ 1/*相当于取反,因为c不是1就是0*/)) return 0;
}
}
}
return 1;
}
bool dp[MAXN][MAXN][2]; // 是的你没有看错,这里还有一个dp
// 而且是非常高级的存在性dp,简称不会的dp
int main() {
cin >> n;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) {
cin >> g[i][j];
}
}
for(int i = 1; i < n; i ++) {
for(int j = i + 1; j <= n; j ++) {
if(!g[i][j] || !g[j][i]) {
//=>!(g[i][j]&&g[j][i])
//=>只有相互认识的时候才不会产生连边,其他的情况都加边放图中
add(i,j);
add(j,i);
}
}
}
for(int i = 1; i <= n; i ++) {
if(vis[i]) continue;
idx ++;
if(!dfs(i,0)) {
cout << "No\n";
return 0; // 这是这段代码最友善的地方,给了部分分且不是多测很好
}
}
dp[0][0][0] = true;
dp[0][0][1] = true;
int mx = n / 2;
for(int i = 1; i <= idx; i ++) {
for(int j = sz[i][0]; j <= mx; j ++){
dp[i][j][0] |= dp[i - 1][j - sz[i][0]][0];
dp[i][j][0] |= dp[i - 1][j - sz[i][0]][1];
// 一个小dp,判断分在哪个桌子
}
for(int j = sz[i][1]; j <= mx; j ++){
dp[i][j][1] |= dp[i - 1][j - sz[i][1]][0];
dp[i][j][1] |= dp[i - 1][j - sz[i][1]][1];
}
}
int ans = 0;
for(int j = mx; j >= 1; j --){
if(dp[idx][j][0] || dp[idx][j][1]){
ans = n - j;
break;
}
}
cout << "Yes" << endl;
cout << ans << endl;
return 0;
}
点赞关注喵,点赞关注谢谢喵。
标签:int,合并,DAY3,long,while,MAXN,补题,dp From: https://www.cnblogs.com/Cherry929/p/18445878