雅礼学习10.7
雅礼学习10.7
上午考试
各题状况
全TM神仙题。。。
\(T1\) \(35\)分暴力
\(T2\) 我\(n=1\)的时候直接输出了\(1\),连这个格子上是否有方块都没判,当时是感觉。。。难道这个部分分也会卡么
结果出题人就这么卡了。。就一分都没有了
太毒瘤了。
\(T3\) 成功骗\(8\)分
做了一段时间之后去做牛客网的来着。
跟人要了份暴力
然后我TM。。从紫名变成灰名了????
题目及考场代码
T1
/*
* 暴力思路:从初始位置开始进行bfs
*/
#include<queue>
#include<cstdio>
#include<algorithm>
const int N=100001;
int n,k,m,s,a[N],dis[N];
bool ban[N];
std::queue<int> que;
void add(int x,int y)
{
if(x+y<=n && !ban[x+y])
{
que.push(x+y);
ban[x+y]=true;
dis[x+y]=dis[x]+1;
}
if(x-y>0 && ban[x-y]==false)
{
que.push(x-y);
ban[x-y]=true;
dis[x-y]=dis[x]+1;
}
}
void solve()
{
que.push(s);
ban[s]=true;
while(!que.empty())
{
int u=que.front();
for(int i=1;i<=k;++i)
{
if(!(k&1) && i&1)
add(u,i);
if(k&1 && !(i&1))
add(u,i);
}
que.pop();
}
}
int main()
{
freopen("reverse.in","r",stdin);
freopen("reverse.out","w",stdout);
scanf("%d%d%d%d",&n,&k,&m,&s);
for(int x,i=1;i<=m;++i)
{
scanf("%d",&x);
ban[x]=true;
}
if(k==1)
{
for(int i=1;i<=n;i++)
if(i==s)
printf("0 ");
else printf("-1 ");
goto E;
}
solve();
for(int i=1;i<=n;++i)
{
if(dis[i]==0)
if(i==s)
printf("0 ");
else
printf("-1 ");
else printf("%d ",dis[i]);
}
E: fclose(stdin),fclose(stdout);
return 0;
}
T2
#include <cstdio>
inline int read()
{
int n=0,w=1;register char c=getchar();
while(c<'0' || c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0' && c<='9')n=n*10+c-'0',c=getchar();
return n*w;
}
const int N=1e5+1;
int n,a[N],b[N];
int main()
{
freopen("silhouette.in","r",stdin);
freopen("silhouette.out","w",stdout);
n=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i)b[i]=read();
if(n==1)
{
printf("1");
goto E;
}
E: fclose(stdin);fclose(stdout);
return 0;
}
T3
/*
*/
#include <cstdio>
int n,p;
inline int ksm(long long n,int b)
{
long long res=1;
for(;b;b>>=1,n=n*n%p)
if(b&1)
res=res*n%p;
return res;
}
int main()
{
freopen("seat.in","r",stdin);
freopen("seat.out","w",stdout);
scanf("%d%d",&n,&p);
if(n==1)
printf("1");
else
if(n==2)
{
int o=ksm(2,p-2);
printf("%d %d\n",o,o);
printf("%d %d\n",o,o);
return 0;
}
else printf("19260817");
fclose(stdin),fclose(stdout);
return 0;
}
正解及代码
T1
考虑当前在每个点时可以通过一次翻转转移到哪些点,直接\(BFS\)即可算出每个点的所需步数。然而边数会达到\(O(n^2)\)级别。
可以发现转移到的点一定是一段区间内的奇数或者偶数点,于是一种简单的优化方法是在\(BFS\)时开两个\(set\)维护当前有哪些奇数点和偶数点还未被\(BFS\)到,转移时直接在\(set\)上\(lowerbound\),就不会访问已经\(BFS\)到过的点了
\(O(n\log n)\)
#include <bits/stdc++.h>
#define For(i, j, k) for (int i = j; i <= k; i++)
using namespace std;
const int N = 1e5 + 10;
int n, K, m, S;
int dis[N];
bool ban[N];
set<int> odd, even;
void BFS() {
memset(dis, -1, sizeof dis);
queue<int> q;
dis[S] = 0, q.push(S);
while (!q.empty()) {
int o = q.front(); q.pop();
int L = max(1, o - K + 1), R = min(n, o + K - 1);
L = L + (L + K - 1) - o, R = R + (R - K + 1) - o;
set<int> &p = L & 1 ? odd : even;
for (auto i = p.lower_bound(L); i != p.end() && *i <= R; p.erase(i++))
dis[*i] = dis[o] + 1, q.push(*i);
}
}
int main() {
freopen("reverse.in", "r", stdin);
freopen("reverse.out", "w", stdout);
scanf("%d%d%d%d", &n, &K, &m, &S);
For(i, 1, m) {
int x;
scanf("%d", &x);
ban[x] = true;
}
For(i, 1, n) if (!ban[i] && i != S) i & 1 ? odd.insert(i) : even.insert(i);
BFS();
For(i, 1, n) printf("%d%c", dis[i], i == n ? '\n' : ' ');
return 0;
}
T2
题目转化:
实际是在问下面的方程组有多少非负整数解:
那么不妨先将\(A,B\)排序,显然这样不会有什么影响。如果\(A_\max\ne B_\max\)那么答案是\(0\),否则一定有解
显然有\(x_{i,j}\le \min(A_i,B_j)\),从大到小枚举每个\(A,B\)中出现的值\(s\),每次确定所有\(\min(A_i,B_j)=s\)的位置的值
- 先来考虑\(s\)为最大值的情况,此时我们要确定的位置构成了一个矩形。可以发现我们要解决这样一个子问题:一个\(a\times b\)的矩形,每个位置的值在\([0,s]\)中,且每行每列的最大值均为\(s\),需要计算有多少取值的方案。
考虑容斥,设\(f(i)\)为至少有\(i\)行的限制不满足条件时的方案(需要保证每一列都满足条件),那么有
方案数就是$\sum_{i=0}^a(-1)^i f(i)$
- \(s\)不是最大值的时候,我们每次要确定的位置可能是\('L'\)形,仍然可以用相同的方法进行容斥
加上快速幂取逆元,总复杂度\(O(n\log n)\)
#include <bits/stdc++.h>
#define For(i, j, k) for (int i = j; i <= k; i++)
#define Forr(i, j, k) for (int i = j; i >= k; i--)
using namespace std;
const int N = 5e5 + 10;
const int Mod = 1e9 + 7;
int Pow(int x, long long e) {
int ret = 1;
while (e) {
if (e & 1) ret = 1ll * ret * x % Mod;
x = 1ll * x * x % Mod;
e >>= 1;
}
return ret;
}
int fac[N], rfac[N];
int work(int a, int b, int la, int lb, int w) {
int ans = 0;
For(i, 0, a) {
int s = Pow(Pow(w + 1, la + a - i) - Pow(w, la + a - i) + Mod, b);
s = 1ll * s * Pow(w + 1, 1ll * lb * (a - i)) % Mod * Pow(w, 1ll * (b + lb) * i) % Mod;
s = 1ll * s * rfac[i] % Mod * rfac[a - i] % Mod;
if (i & 1) ans = (ans - s + Mod) % Mod; else ans = (ans + s) % Mod;
}
return 1ll * ans * fac[a] % Mod;
}
int A[N], B[N], num[N << 1];
int n, c;
int main() {
freopen("silhouette.in", "r", stdin);
freopen("silhouette.out", "w", stdout);
scanf("%d", &n);
For(i, 1, n) scanf("%d", &A[i]), num[++c] = A[i];
For(i, 1, n) scanf("%d", &B[i]), num[++c] = B[i];
fac[0] = 1;
For(i, 1, n) fac[i] = 1ll * fac[i - 1] * i % Mod;
rfac[n] = Pow(fac[n], Mod - 2);
Forr(i, n, 1) rfac[i - 1] = 1ll * rfac[i] * i % Mod;
sort(A + 1, A + n + 1, greater<int>());
sort(B + 1, B + n + 1, greater<int>());
sort(num + 1, num + c + 1, greater<int>());
c = unique(num + 1, num + c + 1) - num - 1;
int la = 0, lb = 0;
int ans = 1;
For(i, 1, c) {
int ra = la, rb = lb;
while (ra < n && A[ra + 1] == num[i]) ++ra;
while (rb < n && B[rb + 1] == num[i]) ++rb;
ans = 1ll * ans * work(ra - la, rb - lb, la, lb, num[i]) % Mod;
if (ans == 0) break;
la = ra, lb = rb;
}
printf("%d\n", ans);
return 0;
}
T3
一个结论是,对于任意一个人,他坐下时离最近的人的距离是固定的,不随前面的人的决策而改变。这样我们可以将所有人按坐下时的距离分成若干层。另一个结论是,无论之前每一层如何决策,轮到这一层时逬空区间的长度构成的可重集也是固定的。
对于最后一层特殊处理,接下来均默认不是最后一层。对于之前的每一层,考虑在哪些空
区间中央坐下可使得距离最大,其中可能会有一些长度为奇数的区间和一些长度为偶数的区间,而对于每个人来说,坐在任意一个奇数的区间的中央的概率都是相等的,偶数同理。
那么我们只需要知道,每个人有多大的概率坐在一个奇或偶数区间的中央。考虑\(dp[i][j]\)表示这一层已经坐下\(i\)个人之后,还有\(j\)个长度为偶数的区间的概率,转移只需考虑当前这个人坐了哪类区间即可。\(dp\)之后容易算出之前要求的概率。
区间长度为奇数时位置是固定的,考虑区间长度为偶数的情况,此时会出现两个位置可
供选择,但无论选择哪一个,都会将区间划分成长度为\(\frac{n}{2}\)和\(\frac{n}{2}-1\)的两段。因此这两种选择具有对称性,我们只需要假定选择其中的一个,算出这种情况下之后的层的答案,即可对称地推出另一种情况下的答案。
瓶颈在利用对称性推答案的地方,复杂度为\(O(n^2\log n)\)
#include <bits/stdc++.h>
#define For(i, j, k) for (int i = j; i <= k; i++)
#define Forr(i, j, k) for (int i = j; i >= k; i--)
using namespace std;
const int N = 1030;
int Mod;
int Pow(int x, int e) {
int ret = 1;
while (e) {
if (e & 1) ret = ret * x % Mod;
x = x * x % Mod;
e >>= 1;
}
return ret;
}
void add(int &x, int y) {
x += y;
x = x >= Mod ? x - Mod : x;
}
int n;
int dp[N][N], f[N][N], g[N][N];
bool vis[N];
int inv[N], cnt[N], odd[N], pos[N];
int main() {
freopen("seat.in", "r", stdin);
freopen("seat.out", "w", stdout);
scanf("%d%d", &n, &Mod);
For(i, 1, n) inv[i] = Pow(i, Mod - 2);
vis[0] = vis[n + 1] = true;
For(i, 1, n) {
int pl = 0, pr = 0;
For(j, 0, n) {
int r = j + 1;
while (!vis[r]) ++r;
if (r - j > pr - pl) pl = j, pr = r;
j = r - 1;
}
int mx = (pr - pl) / 2;
cnt[mx]++;
pos[i] = pl + mx;
vis[pl + mx] = true;
if ((pr - pl) % 2) odd[mx]++;
}
int sum = n;
For(i, 1, n) if (cnt[i]) {
int l = sum - cnt[i] + 1, r = sum;
if (i == 1) {
For(j, l, r) For(k, l, r)
dp[j][pos[k]] = inv[cnt[i]];
} else {
For(j, 0, cnt[i]) For(k, 0, odd[i]) f[j][k] = 0;
f[0][odd[i]] = 1;
int p = l + odd[i] - 1;
For(j, 1, cnt[i]) {
int oddw = 0, evenw = 0;
For(k, 0, odd[i]) if (f[j - 1][k]) {
int frac = (cnt[i] - j + 1) + k, w = 0;
if (k) {
w = f[j - 1][k] * k * 2 % Mod * inv[frac] % Mod;
oddw = (oddw + w * inv[odd[i] * 2]) % Mod;
add(f[j][k - 1], w);
}
if (cnt[i] - odd[i]) {
w = f[j - 1][k] * (frac - 2 * k) % Mod * inv[frac] % Mod;
evenw = (evenw + w * inv[cnt[i] - odd[i]]) % Mod;
add(f[j][k], w);
}
}
For(u, l, p) add(dp[l + j - 1][pos[u]], oddw), add(dp[l + j - 1][pos[u] + 1], oddw);
For(u, p + 1, r) add(dp[l + j - 1][pos[u]], evenw);
}
For(j, l, p) {
int L = pos[j] - i + 1, R = pos[j] + i;
For(v, L, R) if (v != pos[j]) For(u, r + 1, n) {
int s = v < pos[j] ? v + i + 1 : v - i, w = dp[u][v] * inv[2] % Mod;
add(g[u][v], w), add(g[u][s], w);
}
For(v, L, R) For(u, r + 1, n)
dp[u][v] = g[u][v], g[u][v] = 0;
}
}
sum = l - 1;
}
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++)
printf("%d%c", dp[i][j], j == n ? '\n' : ' ');
return 0;
}
下午讲课
例1
\(Berland\)要举行\(n\)次锦标赛,第一次只有一个人,之后每一次会新加入一个人。锦标赛中有\(k\)种运动项目,每个人在这\(k\)种项目上都有一个能力值,每次会选择任意两个还未被淘汰的人进行某个项目的比赛,能力值高的人胜出,输的人被淘汰,直至只剩下一个人成为冠
军。
给出每个人每个项目的能力值,保证它们两两不同,求每次锦标赛有多少人可能成为冠军。
\(n\le 5e4,k\le 10\)
解:
只要选手\(a\)在某个项目上比选手\(b\)强,\(a\)就可以淘汰\(b\),我们可以连一条\(a\)到\(b\)的边。
对整个图求强连通分量,缩点后一定会形成一个竞赛图(任意两点之间有有向边相连),拓扑序最靠前的分量中的所有点都可能成为冠军。
每加入一个点时,我们可能需要合并拓扑序在一段区间内强连通 分量。用\(set\)按拓扑序维护每个强连通分量,对每个分量记录它的大 小,以及在每个项目上的最大和最小能力值,就可以直接在\(set\)上二分找到需要合并的区间。 最多只会合并n−1次,\(O(nk\log n)\)
例2
有\(X+Y+Z\)个人,第\(i\)个人有\(A_i\)个金币,\(B_i\)个银币,\(C_i\)个铜币。 选出\(X\)个人获得其金币,选出\(Y\)个人获得其银币,选出\(Z\)个人获得其铜币。
在不重复选某个人的前提下,最大化获得的币的总数。\(X+Y+Z\le 10^5\)
解:
不妨先令\(A_i=A_i−C_i,B_i=B_i−C_i\),问题变为选出\(X\)个人获得其金币,选出\(Y\)个人获得其银币,再将答案加上\(\sum C_i\). 按\(A_i\)从大到小排序,枚举选出的\(X\)个人中\(A_i\)最小的人,显然这个人之前的人要么在选出的\(X\)个人中,要么在选出的\(Y\)个人中。 那么我们只要对每个位置\(i\),\(X\le i\le X+Y\)计算两个信息:\(i\)之前\(A_i−B_i\)最大的\(X\)个人的\(A_i −B_i\)的和,\(i\)之后\(B_i\)最小的\(Z\)个人的\(B_i\)之和 。 于是我们只需要从前往后扫一遍,用小根堆维护当前\(A_i−B_i\)最大的\(X\)个人,每加入一个人与堆顶比较;再从后往前算第二个信息即可。\(O(n\log n)\)
例3
有一个\(1\)到\(n\)的排列\(p\),我们称一个子区间是好的,当且仅当这个子区间内的值构成了连续的一段(例如对于排列\(\{1,3,2\},[1,1], [2,2], [3,3], [2,3],[1,3]\)是好的区间)。
\(q\)次询问,每次询问\(L,R\), 求有多少\(L\le l\le r\le R\),满足\([l,r]\)是好的区间。
\(n,q\le 1.2×10^5,TL=7s\)
解:
一段区间是好的,当且仅当\((\max−\min)−(r−l)=0\). 考虑从小到大枚举\(r\),用线段树对每个l维护上式的值。维护两个栈,分别保存\(l\)在每段区间中时\(max\)和\(min\)的值,维护栈的同时在线段树上修改对应区间的值。
线段树上需要维护最小值以及最小值数量,若最小值为\(0\)(实际上全局最小值一定是\(0\)),则所有最小值的位置对于当前的\(r\)都是合法的左端点。于是线段树上存一个“所有最小值的位置的答案”的加标记,并维护区间答案之和。
将询问离线按\(R\)排序,我们就可以在\(R\)处查询区间答案之和来回答询问。
\(O(n\log n)\)
例4
有\(10^5\)个初始为空的集合。对于一个集合,若其中没有元素\(x\)满足\(x\gt 0\),或没有元素\(x\)满足\(x\lt 0\),则定义其优美度为\(0\);否则其优美度等于最小的正的元素减去其最大的负的元素。
\(q\)次操作,每次操作要么向一个区间内的集合加入某个元素,要么询问一个区间内集合的优美度之和。\(q\le 5\times 10^4,TL=4s\)
解:
对于正的和负的元素分开考虑。如果每个集合都有正的和负的元素,那么这道题就是要支持区间取\(\min\)以及区间求和。
这可以对每个位置维护最大值\(\max\)和次大值\(\sec\),以及最大值数量和一个取\(\min\)的标记。如果现在要对\(x\)取\(\min\),分情况讨论:
- 若\(x\ge \max\)直接忽略该操作
- 若\(sec\lt x\lt \max\),更新最大值,根据记录的最大值数量快速算出新的区间和。
- 若\(sec\le x\),直接暴力下传。(可用势能分析证明暴力下传次数为\(O(q\log n)\)级别)。
回到本题,唯一要注意的是如果一个集合还没有正的或负的元素就不要计入它的信息,当一个集合第一次加入正的或负的元素时,可以暴力处理。\(O((n+q)\log n)\)
从相识到现在,从冷淡到关怀,从拒绝到依赖,从陌生到相爱,从深信到疑猜,从疼爱到伤害,从炫烂到苍白,从厮守到分开,从感动到感慨,从体谅到责怪,从期待到无奈,从狂喜到悲哀
当你尝尽人情冷暖,当你决定为了你的理想燃烧,扪心自问,生活的压力与生命的尊严哪一个更重要?
想得却不可得,你奈人生何。
该舍的舍不得,只顾着与俗事纠葛。
等你发现妥协是贼了,它早已偷光你的选择。
名利不过是一场高烧,遗憾是紧跟著的、好不了的咳。
我可以原谅,却无法阻挡。
独自在夜里感伤。
是年少轻狂,却故作沧桑。
谁给你勇气去伪装。
丢弃的理想像极了一个巴掌。
每当你记起一句就挨一个耳光。
然后就开始反省着、痛恨着自己的脏。