海亮 7.5 模拟赛
海亮7.5模拟赛
#A. 道路负载
将每一条边的边权设置为它连接的两个点的权值最小值 然后我们从小到大加边并统计两个集合之间的权值
按照从大到小加边策略的原因:
你先弄出一条大边 那么无论如何这两个点之间的\(c''\)就是这条大边 且在这两个点所代表的所有集合的所有点都可以通过这一条边连通
如果左面或者右面集合有大边的话 左右一定是通过这条"较小边"连通的 而且左面右面集合有小边的情况是不存在的 所以保证了正确性
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 4e5 + 5;
const int inf = 0x3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , ans , a[N];
int fa[N] , sz[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
return fa[x] = find ( fa[x] );
}
struct nn { int u , v , val; } e[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
for ( int i = 1 ; i <= m ; i ++ )
{
int u = read() , v = read();
e[i].u = u , e[i].v = v;
e[i].val = min ( a[u] , a[v] );
}
sort ( e + 1 , e + m + 1 , [](const nn a , const nn b) { return a.val > b.val; } );
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i , sz[i] = 1;
for ( int i = 1 ; i <= m ; i ++ )
{
int fu = find ( e[i].u ) , fv = find ( e[i].v );
if ( fu == fv ) continue;
ans += sz[fu] * sz[fv] * e[i].val;
fa[fu] = fv;
sz[fv] += sz[fu];
}
cout << ans << endl;
return 0;
}
#B. 小根堆
如果没有题目中的条件限制 那么我们设置\(f[x]\)为以这个点为根的小根堆的方案个数
那么\(f[x]=C_{sz[x]-1}^{sz[ls]}*f[ls]*f[rs]\)
可以\(dfs\)提前维护数组\(sz\)和\(f[i]\) 也可以记忆化写
对于询问\(x,y\) 对它有影响的只有\(x\)到根节点的\(logn\)个点
\(g[x][y]\)为第\(x\)个节点且这个点值为\(y\)的方案数
那么初始值\(g[x][y]= dp(x) * C ( n - y , sz[x] - 1 )\)
考虑到只有\(x\)到根上的所有节点会被考虑到 那么我们可以将\(x\)一维滚掉
先将前一个\(x\)记录在\(temp\)中 再将\(x>>1\) 那么柿子就是:
\(g[i] = sum[i+1] \% mod * dp(temp^1) \% mod * C ( n - i - sz[temp] , sz[temp^1] );\)
非常优美的结论:方案数=排列数*选值数
记录一下调试的错误:
- 后缀和求成了前缀和
- 阶乘从1开始了
- 未知错误:只有记忆化写\(f[i]\)才能过 否则无法通过
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define ls (x<<1)
#define rs (x<<1|1)
#define int long long
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , fac[N] , inv[N] , sz[N] , f[N] , g[N] , x , y , sum[N];
void init ()
{
fac[0] = inv[0] = fac[1] = inv[1] = 1;
for ( int i = 2 ; i <= n ; i ++ )
inv[i] = ( ( ( mod - mod / i ) * inv[mod%i] ) % mod + mod ) % mod , fac[i] = fac[i-1] * i % mod;
for ( int i = 2 ; i <= n ; i ++ ) // 求阶乘逆元
inv[i] = inv[i-1] * inv[i] % mod;
}
int C ( int a , int b )//a中b个
{
if ( b > a ) return 0;
return fac[a] % mod * inv[b] % mod * inv[a-b] % mod;
}
void dfs ( int x )
{
sz[x] = 1;
if ( ls <= n ) dfs ( ls ) , sz[x] += sz[ls];
if ( rs <= n ) dfs ( rs ) , sz[x] += sz[rs];
}
int dp ( int x )
{
if ( f[x] != -1 ) return f[x];
if ( sz[x] < 3 ) return 1;//只有一种情况
int ans = dp(ls) * dp(rs) % mod * C ( sz[x] - 1 , sz[ls] ) % mod;
return f[x] = ans;
}
void solve()
{
g[y] = dp(x) * C ( n - y , sz[x] - 1 ) % mod;
while ( x != 1 )
{
int temp = x; x >>= 1;
for ( int i = n ; i ; i -- )
sum[i] = ( sum[i+1] + g[i] ) % mod;
for ( int i = 1 ; i <= n ; i ++ )
g[i] = sum[i+1] % mod * dp(temp^1) % mod * C ( n - i - sz[temp] , sz[temp^1] );
}
cout << g[1] << endl;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
memset ( f , -1 , sizeof f );
n = read() , x = read() , y = read();
init();
dfs(1);
solve();
return 0;
}
#C. 模式匹配
我们考虑贪心 对于四元组肯定是越早取越好
所以我们记录每一个值的前一个值\(pre[i]\)(见\(hh\)的项链)
那么对于一个二元组的第二个值 那么查询左面的端点有没有值 如果有就计入答案同时向右面移动区间
否则继续覆盖区间
注意特判四个点都一样的情况(具体实现为设置\(chk\)数组) \(last\)必须初始化为\(1\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , a[N] , lst[N] , pre[N] , chk[N] , t[N] , ans;
int lowbit ( int x ) { return x & (-x); }
void upd ( int x , int val )
{
for ( int i = x ; i <= n ; i += lowbit(i) ) t[i] += val;
}
int query ( int x )
{
int res = 0;
for ( int i = x ; i ; i -= lowbit(i) )
res += t[i];
return res;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ )
{
a[i] = read();
pre[i] = lst[a[i]];
lst[a[i]] = i;
}
int lstt = 1;
for ( int i = 1 ; i <= n ; i ++ )
{
++chk[a[i]];
if ( chk[a[i]] == 4 )
{
for ( int j = lstt ; j <= i ; j ++ ) --chk[a[j]];
ans += 4 , lstt = i + 1; continue;
}
if ( pre[i] < lstt ) continue;
if ( query(pre[i]) > 0 )
{
for ( int j = lstt ; j <= i ; j ++ ) --chk[a[j]];
ans += 4 , lstt = i + 1;
}
upd ( pre[i] + 1 , 1 ) , upd ( i , -1 );
}
cout << ans << endl;
return 0;
}