未闻花名
昨天成人礼,然后就有了今天的题目。
(番里刀子很多,大家可以去看一下)
当然这是一篇题解。
2023冲刺清北营4
T1 猴戏世家
考虑到一些篱笆之间存在包含关系,我们将第一个包含当前点的篱笆设为当前点的父亲,如果我们得到了所有篱笆的父子关系,考虑模拟删除所有篱笆的过程,容易发现当前篱笆的答案可以贡献到第一个没有删去的且包含它的篱笆,也就是第一个没有被删去的祖先,可以用并查集进行维护。
因此考虑求解所有篱笆的父子关系,从右向左进行扫描线,用 map 维护包含当前 \(x\) 坐标上所有点的篱笆构成的集合,考虑加入一个猴子,发现当前猴子可以贡献到包含它的篱笆,考虑加入一个篱笆,首先在 map 中二分找到包含当前点的篱笆,并设置父子关系,然后从包含当前点的篱笆开始,按照 \(y\) 坐标递减的顺序遍历 map 中所有篱笆,如果遍历到的篱笆的出现时间晚于当前篱笆,显然被遍历到的篱笆没有可以控制的范围了,因此将其从 map 中删除,继续遍历,如果遍历到的篱笆的出现时间早于当前篱笆,显然当前篱笆没有更靠下的控制范围了,因此直接 break ,容易发现复杂度为 \(O(n\log n)\) 。
code
#include <cstdio>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
using namespace std;
const int max1 = 3e5;
const int inf = 0x3f3f3f3f;
int n, m, ans[max1 + 5];
struct Point
{
int x, y, id;
Point () {}
Point ( int __x, int __y, int __id )
{ x = __x, y = __y, id = __id; }
bool operator < ( const Point &A ) const
{ return x < A.x; }
}point[max1 + 5], line[max1 + 5];
map <int, int> Map;
int v[max1 + 5];
struct Union_Find_Set
{
int father[max1 + 5];
void Build ()
{
for ( int i = 1; i <= m; i ++ )
father[i] = i;
return;
}
int Get_Father ( int x )
{
if ( father[x] != x )
father[x] = Get_Father(father[x]);
return father[x];
}
void Merge ( int x, int y )
{
int r1 = Get_Father(x), r2 = Get_Father(y);
if ( r1 == r2 )
return;
ans[r1] += ans[r2];
father[r2] = r1;
return;
}
}Set;
int main ()
{
freopen("monkey.in", "r", stdin);
freopen("monkey.out", "w", stdout);
scanf("%d", &n);
for ( int i = 1; i <= n; i ++ )
{ scanf("%d%d", &point[i].x, &point[i].y); point[i].id = i; }
scanf("%d", &m);
for ( int i = 1; i <= m; i ++ )
{ scanf("%d%d", &line[i].x, &line[i].y); line[i].id = i; }
sort(point + 1, point + 1 + n);
sort(line + 1, line + 1 + m);
Map.insert(make_pair(inf, 0));
Map.insert(make_pair(0, 0));
int now = n;
for ( int i = m; i >= 1; i -- )
{
while ( now && point[now].x > line[i].x )
{
int x = Map.lower_bound(point[now--].y) -> second;
++ans[x];
}
map <int, int> :: iterator start = Map.upper_bound(line[i].y);
v[line[i].id] = start -> second;
while ( true )
{
start = --Map.upper_bound(line[i].y);
if ( start == Map.begin() || ( start -> second ) < line[i].id )
break;
Map.erase(start);
}
Map.insert(make_pair(line[i].y, line[i].id));
}
while ( now )
{
int x = Map.lower_bound(point[now--].y) -> second;
++ans[x];
}
Set.Build();
for ( int i = m; i >= 1; i -- )
Set.Merge(v[i], i);
for ( int i = 1; i <= m; i ++ )
printf("%d\n", ans[i]);
return 0;
}
T2 零糖麦片
考虑转化答案的求解式:
考虑利用乘法分配律拆解上述式子,假设当前确定了排列 \(p\) ,设 \(a_{i,p_i}\) 构成的集合为 \(S\) ,我们可以理解为在 \(S\) 中选出一个子集 \(T\) ,贡献为 \(\prod_{i\in T}(w_i-1)\) 。
因此考虑枚举集合 \(T\) ,显然剩余位置任意选择构成 \(S\) 的方案数为 \((n-|T|)!\) ,因此答案的计算式为:
其中 \(T\) 需要满足 \(x_i\) 和 \(y_i\) 互不相同。
发现 \(T\) 的限制是二分图匹配的过程,那么二分图中的边的贡献为 \(w_i-1\) ,一个匹配的权值就是所有匹配边的贡献的乘积,容易发现不连通的两个二分图的贡献可以直接合并,因此考虑对一个联通的二分图进行求解。
由于二分图中边数最多为 \(k\) ,因此点数最多为 \(k+1\) ,我们对点数较少的一边进行状压,可以用复杂度 \(O(2^{\tfrac{k}{2}}k^2)\) 的状压 dp 求解,显然无法通过本题。
考虑二分图中点数较多的时候,整张图接近一棵树,因此我们可以任意求解出二分图中的一棵生成树,设点数为 \(s\) ,那么生成树外存在 \(k-s\) 条返祖边,可以 \(2^{k-s}\) 暴力枚举所有返祖边是否匹配,然后对树边做树形 dp ,可以在 \(O(2^{k-2}s^2)\) 的复杂度内求解。
简单平衡复杂度,当 \(s\le \tfrac{2}{3}k\) 时运用第一种方法,否则运用第二种方法,可以在 \(O(2^{\tfrac{k}{3}}k^2)\) 的复杂度内通过。
code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;
const int mod = 1e9 + 7;
const int max1 = 1e5, max2 = 50, max3 = 1 << 18, B = 36;
int n, k, fac[max1 + 5];
vector < pair <int, int> > edge[max1 * 2 + 5];
vector < pair <int, int> > tree[max1 * 2 + 5];
vector < pair < pair <int, int> , int > > save;
bool choose[max1 * 2 + 5];
int x[max2 + 5], y[max2 + 5], w[max2 + 5];
bool vis[max1 * 2 + 5];
int savex[max2 + 5], lenx, savey[max2 + 5], leny, Map[max1 * 2 + 5];
int f[max2 + 5][max3 + 5], g[max1 + 5][max2 + 5][2], h[max2 + 5][2];
void Add ( int &x, int y )
{
x = x + y;
if ( x >= mod )
x = x - mod;
return;
}
struct Poly
{
int F[max2 + 5];
void Clear ()
{
for ( int i = 0; i <= k; i ++ )
F[i] = 0;
return;
}
void Insert ( int pos, int x )
{ Add(F[pos], x); return; }
Poly operator * ( const Poly &A ) const
{
Poly res;
for ( int i = 0; i <= k; i ++ )
res.F[i] = 0;
for ( int i = 0; i <= k; i ++ )
for ( int j = 0; i + j <= k; j ++ )
res.F[i + j] = ( res.F[i + j] + 1LL * F[i] * A.F[j] ) % mod;
return res;
}
}ans, tmp;
void Dfs ( int now )
{
if ( vis[now] )
return;
if ( now <= n )
savex[++lenx] = now;
else
savey[++leny] = now;
vis[now] = true;
for ( auto v : edge[now] )
Dfs(v.first);
return;
}
void Redfs ( int now, int fa )
{
vis[now] = true;
for ( auto v : edge[now] )
{
if ( v.first == fa )
continue;
if ( !vis[v.first] )
{
tree[now].push_back(v);
Redfs(v.first, now);
}
else
{
if ( now < v.first )
save.push_back(make_pair(make_pair(now, v.first), v.second));
}
}
return;
}
void DP ( int now )
{
for ( int i = 0; i <= k; i ++ )
g[now][i][0] = g[now][i][1] = 0;
if ( choose[now] )
g[now][0][0] = 0, g[now][0][1] = 1;
else
g[now][0][0] = 1, g[now][0][1] = 0;
for ( auto v : tree[now] )
{
DP(v.first);
for ( int i = 0; i <= k; i ++ )
for ( int j = 0; j < 2; j ++ )
h[i][j] = g[now][i][j], g[now][i][j] = 0;
for ( int i = 0; i <= k; i ++ )
{
for ( int j = 0; i + j <= k; j ++ )
{
g[now][i + j][0] = ( g[now][i + j][0] + 1LL * h[i][0] * ( g[v.first][j][0] + g[v.first][j][1] ) ) % mod;
g[now][i + j][1] = ( g[now][i + j][1] + 1LL * h[i][1] * ( g[v.first][j][0] + g[v.first][j][1] ) ) % mod;
if ( i + j + 1 <= k )
g[now][i + j + 1][1] = ( g[now][i + j + 1][1] + 1LL * h[i][0] * g[v.first][j][0] % mod * v.second ) % mod;
}
}
}
return;
}
void Solve ( int p )
{
lenx = leny = 0;
Dfs(p);
sort(savex + 1, savex + 1 + lenx);
sort(savey + 1, savey + 1 + leny);
if ( lenx + leny == 1 )
return;
if ( lenx + leny <= B )
{
if ( lenx >= leny )
{
for ( int i = 1; i <= leny; i ++ )
Map[savey[i]] = i;
for ( int s = 0; s < 1 << leny; s ++ )
f[0][s] = 0;
f[0][0] = 1;
for ( int i = 1; i <= lenx; i ++ )
{
int now = savex[i];
for ( int s = 0; s < 1 << leny; s ++ )
f[i][s] = f[i - 1][s];
for ( auto v : edge[now] )
{
int t = Map[v.first];
for ( int s = 0; s < 1 << leny; s ++ )
if ( f[i - 1][s] && !( s >> t - 1 & 1 ) )
Add(f[i][s | ( 1 << t - 1 )], 1LL * f[i - 1][s] * v.second % mod);
}
}
tmp.Clear();
for ( int s = 0; s < 1 << leny; s ++ )
tmp.Insert(__builtin_popcount(s), f[lenx][s]);
}
else
{
for ( int i = 1; i <= lenx; i ++ )
Map[savex[i]] = i;
for ( int s = 0; s < 1 << lenx; s ++ )
f[0][s] = 0;
f[0][0] = 1;
for ( int i = 1; i <= leny; i ++ )
{
int now = savey[i];
for ( int s = 0; s < 1 << lenx; s ++ )
f[i][s] = f[i - 1][s];
for ( auto v : edge[now] )
{
int t = Map[v.first];
for ( int s = 0; s < 1 << lenx; s ++ )
if ( f[i - 1][s] && !( s >> t - 1 & 1 ) )
Add(f[i][s | ( 1 << t - 1 )], 1LL * f[i - 1][s] * v.second % mod);
}
}
tmp.Clear();
for ( int s = 0; s < 1 << lenx; s ++ )
tmp.Insert(__builtin_popcount(s), f[leny][s]);
}
}
else
{
for ( int i = 1; i <= lenx; i ++ )
{
vis[savex[i]] = false;
tree[savex[i]].clear();
}
for ( int i = 1; i <= leny; i ++ )
{
vis[savey[i]] = false;
tree[savey[i]].clear();
}
save.clear();
Redfs(p, 0);
int len = save.size();
tmp.Clear();
for ( int s = 0; s < 1 << len; s ++ )
{
for ( int i = 1; i <= lenx; i ++ )
choose[savex[i]] = false;
for ( int i = 1; i <= leny; i ++ )
choose[savey[i]] = false;
int cnt = 0, val = 1;
bool illegal = false;
for ( int i = 0; i < len; i ++ )
{
if ( s >> i & 1 )
{
if ( choose[save[i].first.first] || choose[save[i].first.second] )
{ illegal = true; break; }
++cnt, val = 1LL * val * save[i].second % mod;
choose[save[i].first.first] = choose[save[i].first.second] = true;
}
}
if ( illegal )
continue;
DP(p);
for ( int i = 0; i + cnt <= k; i ++ )
tmp.Insert(i + cnt, 1LL * val * ( g[p][i][0] + g[p][i][1] ) % mod);
}
}
ans = ans * tmp;
return;
}
int main ()
{
freopen("cereal.in", "r", stdin);
freopen("cereal.out", "w", stdout);
scanf("%d%d", &n, &k);
fac[0] = 1;
for ( int i = 1; i <= n; i ++ )
fac[i] = 1LL * fac[i - 1] * i % mod;
for ( int i = 1; i <= k; i ++ )
{
scanf("%d%d%d", &x[i], &y[i], &w[i]);
edge[x[i]].push_back(make_pair(n + y[i], ( w[i] + mod - 1 ) % mod));
edge[n + y[i]].push_back(make_pair(x[i], ( w[i] + mod - 1 ) % mod));
}
ans.Clear();
ans.Insert(0, 1);
for ( int i = 1; i <= n; i ++ )
if ( !vis[i] )
Solve(i);
int sum = 0;
for ( int i = 0; i <= min(n, k); i ++ )
sum = ( sum + 1LL * fac[n - i] * ans.F[i] ) % mod;
printf("%d\n", sum);
return 0;
}
T3 文体双花
签到题,考虑 dp ,设 \(f_i\) 表示以 \(i\) 结尾的划分数,显然 \(f_{i}=\sum_{k}f_k\) ,其中 \([k+1,i]\) 区间内取值连续,容易发现区间内取值连续的充要条件为 \(\max-\min=R-L\) ,显然可以用线段树维护最小值及最小值个数。
code
#include <cstdio>
using namespace std;
const int max1 = 1e5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int n, p[max1 + 5];
struct Data
{
int minval, count;
Data () {}
Data ( int __minval, int __count )
{ minval = __minval, count = __count; }
Data operator + ( const Data &A ) const
{
if ( minval < A.minval )
return *this;
else if ( minval > A.minval )
return A;
return Data(minval, ( count + A.count ) % mod);
}
};
struct Segment_Tree
{
#define lson(now) ( now << 1 )
#define rson(now) ( now << 1 | 1 )
struct Struct_Segment_Tree
{ Data d; int tag; } tree[max1 * 4 + 5];
void Build ( int now, int L, int R )
{
tree[now].tag = 0;
if ( L == R )
{ tree[now].d.minval = L + 1; tree[now].d.count = 0; return; }
int mid = L + R >> 1;
Build(lson(now), L, mid);
Build(rson(now), mid + 1, R);
tree[now].d = tree[lson(now)].d + tree[rson(now)].d;
return;
}
void Update ( int now, int x )
{
tree[now].d.minval += x;
tree[now].tag += x;
return;
}
void Push_Down ( int now )
{
if ( tree[now].tag )
{
Update(lson(now), tree[now].tag);
Update(rson(now), tree[now].tag);
tree[now].tag = 0;
}
return;
}
void Insert ( int now, int L, int R, int ql, int qr, int x )
{
if ( L >= ql && R <= qr )
return Update(now, x);
Push_Down(now);
int mid = L + R >> 1;
if ( ql <= mid )
Insert(lson(now), L, mid, ql, qr, x);
if ( qr > mid )
Insert(rson(now), mid + 1, R, ql, qr, x);
tree[now].d = tree[lson(now)].d + tree[rson(now)].d;
return;
}
void Insert ( int now, int L, int R, int pos, int x )
{
if ( L == R )
{ tree[now].d.count = x; return; }
int mid = L + R >> 1;
if ( pos <= mid )
Insert(lson(now), L, mid, pos, x);
else
Insert(rson(now), mid + 1, R, pos, x);
tree[now].d = tree[lson(now)].d + tree[rson(now)].d;
return;
}
Data Query ( int now, int L, int R, int ql, int qr )
{
if ( L >= ql && R <= qr )
return tree[now].d;
Push_Down(now);
int mid = L + R >> 1;
Data res = Data(inf, 0);
if ( ql <= mid )
res = res + Query(lson(now), L, mid, ql, qr);
if ( qr > mid )
res = res + Query(rson(now), mid + 1, R, ql, qr);
return res;
}
}Tree;
int mins[max1 + 5], mintop, maxs[max1 + 5], maxtop;
int f[max1 + 5];
int main ()
{
freopen("flower.in", "r", stdin);
freopen("flower.out", "w", stdout);
scanf("%d", &n);
for ( int i = 1; i <= n; i ++ )
scanf("%d", &p[i]);
Tree.Build(1, 0, n);
f[0] = 1;
for ( int i = 1; i <= n; i ++ )
{
while ( mintop && p[mins[mintop]] > p[i] )
{
Tree.Insert(1, 0, n, mins[mintop - 1], mins[mintop] - 1, p[mins[mintop]] - p[i]);
--mintop;
}
while ( maxtop && p[maxs[maxtop]] < p[i] )
{
Tree.Insert(1, 0, n, maxs[maxtop - 1], maxs[maxtop] - 1, p[i] - p[maxs[maxtop]]);
--maxtop;
}
mins[++mintop] = i;
maxs[++maxtop] = i;
Tree.Insert(1, 0, n, i - 1, f[i - 1]);
Data d = Tree.Query(1, 0, n, 0, i - 1);
if ( d.minval == i )
f[i] = d.count;
}
printf("%d\n", f[n]);
return 0;
}

浙公网安备 33010602011771号