本人自是来赛后补题,优质题解轻参考:"蔚来杯"2022牛客暑期坐牢训练营1 A C D G I - 哔哩哔哩 (bilibili.com)
AGD不讲
C: 一道关于斜率的题目,我们可以将每个人可以挡住的区间统计起来,对于没被挡住的区间就为我们要的答案,可以将每个人对挡住后面的贡献来考虑:
从(0,1)到该点来考虑,我们将屏幕上的点进行移动,直到(0,m)会发现该点会将该点与(0,0),(0,m)连线包围的点全部覆盖,导致后面无法没观察到,所以我们利用这个特性,记录一个p[i]
代表第i行的情况(没被覆盖)
#include#include using namespace std; #define int long long const int maxn = 2e5 + 10; int n, m, k, q; int a[maxn], b[maxn]; int p[maxn],ans[maxn]; void solve() { for (int i = 0; i < m; i++)p[i] = n + 1;//初始化 //先利用k个点来更新答案 for (int i = 1; i <= k; i++) p[b[i]] = min(p[b[i]], a[i]); for (int i = 0; i < m; i++)ans[i] = p[i] - 1; //接下来利用斜率 int x1 = n + 1, y1 = 0; for (int i = 0; i y1*x)//更新斜率最大 { x1 = x, y1 = y; } int tmp = n;//斜率为0时 if (y1 != 0) { tmp = (x1*i - 1) / y1;//向下取整,即将恰好经过的点也去掉 } ans[i] = min(ans[i], tmp); } //更新由m-1发射出来的那条线 reverse(p, p + m);//作用是避免斜率为负 x1 = n + 1, y1 = 0; for (int i = 0; i < m; i++) { int x = p[i], y = i; if (y*x1 > y1*x)//更新斜率最大 { x1 = x, y1 = y; } int tmp = n;//斜率为0时 if (y1 != 0) { tmp = (x1*i - 1) / y1;//向下取整,即将恰好经过的点也去掉 } ans[m-i-1] = min(ans[m-i-1], tmp); } int sum = 0; for (int i = 0; i < m; i++) sum += ans[i]; cout << sum << endl; } signed main() { cin >> n >> m >> k >> q; for (int i = 1; i <= k; i++) { cin >> a[i] >> b[i];// b[i]--;//我们将其往下转移,便于维护斜率 } while (q--) { int x, y,z; cin >> x >> a[x] >> b[x]; b[x]--; solve(); } }
I:题目要求目前由13张牌,请问凑成7对摸牌期望是多少轮
此时我们可以发现,这题与期望有关,一般期望题均和dp有联系,所以我们尝试考虑期望dp
首先对于对数,我们要考虑到7对是任意的,即最后摸出来的是什么对没有关系,所以尝试维护此时dp[i];i代表对数,意识为此时手中有i对到达胡排的最小轮,但我们发现此时不经与对数有关,因为我们会将牌给扔掉,那么库存的排数就不一定是固定的,所以维护一个dp[i][j]代表我们此时有i对,库存有j张牌,
考虑转移,从dp[i][j]每次最多处理一张,所以这张可能摸到一对的,也可能摸到单的,不管怎么样,我们都要将单的扔掉:
假如我们仔细考虑会发现我们有有点不确定那就是我们目前牌堆里有多少可以和我们目前排配对的排数?这个是不确定的,当为了使我们胡牌的轮数尽可能少,我们要将牌堆里可以用来胡牌的数尽可能多,那么我们如何操作来使牌堆里的可以成对的牌尽可能多呢,首先我们开始13张牌,有一些没成对,那么我们摸牌,假如是和这每成对的成对,我们就将其他没成对的牌丢掉1张,假如是单的话,我们就将我们摸到的牌丢掉,保证了此时我们牌堆里可以和我们目前牌成对的排数是(13-2*dui(对数))*3;
#include#include #include
J:思路写在代码里了:
#include#include #include using namespace std; #define int long long const int maxn = 2e5 + 10; vector v[maxn]; int fa[maxn], siz[maxn],vis[maxn]; int find(int x) { if (x == fa[x])return x; else return fa[x] = find(fa[x]); } void merge(int x, int y){ siz[y] += siz[x], vis[x] = 1; fa[x] = y; } bool check(int x) { if (vis[x] || v[x].empty())return 0;//代表该点已经是被合并过的点了,或者他不需要子节点来转移 int len = v[x].size(); int now = -1;//x所有子节点都是一个集合:即是否可以从一个S->x for (int i = 0; i < len; i++) { if (now == -1)now = find(v[x][i]); else if (now != find(v[x][i])) return 0; } if (now == x) return 0;//代表此时已经为一个集合里,不需要合并 else { return 1; } } signed main() { int t,tmp=0; cin >> t; while (t--) { int n; cin >> n; for (int i = 1; i <= n;i++)vis[i]=0,v[i].clear(), fa[i] = i, siz[i] = 1; for (int i = 1; i <= n; i++) { int k; cin >> k; for (int j = 1; j <= k; j++) { int x; cin >> x; v[i].push_back(x); } } while (1)//开始合并 { bool flag = 0;//代表此时是否可以合并 for (int i = 1; i <= n; i++) { if (check(i))//代表i可以被合并 { flag = 1; merge(find(i), find(v[i][0])); } } if (!flag) break; } int ans = 0; for (int i = 1; i <= n; i++) ans = max(ans, siz[i]); printf("Case #%lld: %lldn", ++tmp, ans); } }



