ABC 267
E (二分、图论)
Problem
给定一个\(n\)个点\(m\)条边的无向图,每个点有一个点权,现在可以进行\(n-1\)次操作,每次操作可以删掉一个图中还存在的点,这次操作的代价是与这个点直接相连的点的点权和。最后这\(n-1\)次操作的最终代价是所有操作中代价最大的那个。问可能的最小最终代价是多少.
\(1\le n,m\le 2\times 10^5\)
Solve
看到最大中的最小,应该考虑二分答案,但如何检查答案。
我们一开始先计算每个点作为一个点删除时的代价\(s_i\),考虑当前二分答案为\(x\),并且这个\(x\)作为我们可选代价的上界,也就是说我们删除一个点的代价不能超过\(x\),那么考虑BFS删点,一开始把所有满足\(s_i\le x\)的点入队,因为不能一开始就选个超过上界代价的点作为初始点删,假设第一轮当前队头的点是\(u\),并且下一个没有访问过的点是\(v\),如果当前\(v\)不是那些可选的初始点,那么\(s_v\gt x\),如果删掉\(u\)的话会使得\(s_v\)减小\(w_u\)的点权,如果此时\(s_v\le x\),那么就把\(v\)入队,否则说明它周围还要继续删点后才能删除它,就不可以入队;如果\(v\)是一个初始点,说明它的\(s_v\le x\),那么可以直接删除,而删除它也只可能会使得那些还未入队的点的点权变小。
也就是说贪心删点,每次之删掉更新后\(s_v\le x\)的\(v\),最后检查所有点是否均入队,是,则说明上界可以更小,否则说明上界要更大。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
vector<ll>a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
vector<vector<int>>E(n+1);
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
vector<ll>s(n+1);
ll l=0,r=1LL<<60;
auto check=[&](ll x)->bool{
vector<ll>sum(n+1);
vector<int>vis(n+1);
for(int u=1;u<=n;u++)
for(auto v:E[u]) sum[u]+=a[v];
queue<pair<ll,int>>q;
for(int i=1;i<=n;i++)
if(sum[i]<=x) q.push({sum[i],i});
while(q.size()){
auto t=q.front();
q.pop();
int w=t.first,u=t.second;
if(vis[u]) continue;
vis[u]=1;
for(auto v:E[u]){
if(sum[v]<=x) continue;
sum[v]-=a[u];
if(sum[v]<=x && !vis[v]){
q.push({sum[v],v});
}
}
}
for(int i=1;i<=n;i++) if(!vis[i]) return false;
return true;
};
ll ans=-1;
while(l<=r){
ll mid=(l+r)>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<'\n';
}
F.Exactly K Steps(树的直径、性质、图论)
Problem
给定一个\(n\)个点的树,树上两个点之间的路径为两个点直接的边数,现在有\(q\)次询问,每次询问给定一组\((u_i,k_i)\),要求输出任意一个于\(u_i\)之间距离为\(k_i\)的点
\(1\le n,q\le 10^5\)
Solve
首先,可能不能对于每个询问一个一个地DFS去找,时间复杂度\(O(n^2)\)不可以接受
所以考虑把询问离线下来。然后我们考虑树的直径的两个端点\(L\)和\(R\),对于一个对点任意一对点\((u,v)\),有\(d(u,v)\le \max(d(u,L),d(u,R))\),考虑证明:如果存在一对点\((A,B)\),使得\(d(A,B)\gt \max(d(A,L),d(A,R))\),令\(a\)是\(L\)和\(R\)在以\(A\)为根时的LCA,\(b\)同样的定义,则\(d(A,a)+d(a,b)+d(b,B)\ge d(A,B)\gt d(A,R)=d(A,a)+d(a,R)\),因此\(d(a,b)+d(b,B)\gt d(a,R)\),所以\(d(L,B)=d(L,a)+d(a,b)+d(b,B)\gt d(L,a)+d(a,R)=d(L,R)\),与\(d(L,R)\)是最长路径矛盾。
所以我们直接分别以\(L\)和\(R\)作根进行一遍DFS,然后记录一下递归栈记录答案即可。因为对\(u_i\),与它距离为\(k_i\)的点\(v_i\)要么在\(L\rightarrow u_i\)的路径上,要么在\(R\rightarrow v_i\)的路径上。
Code
#include <bits/stdc++.h>
using namespace std;
int main(){
//ios::sync_with_stdio(false);
//cin.tie(nullptr);
int n;
cin>>n;
vector<vector<int>>E(n+1);
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
int q;
cin>>q;
vector<vector<pair<int,int>>>qry(n+1);
for(int i=1;i<=q;i++){
int u,k;
cin>>u>>k;
qry[u].push_back({i,k});
}
auto bfs=[&](int u)->int{
queue<int>q;
vector<int>dist(n+1,n);
dist[u]=0;
q.push(u);
while(q.size()){
int u=q.front();
q.pop();
for(auto v:E[u]){
if(dist[v]>dist[u]+1){
dist[v]=dist[u]+1;
q.push(v);
}
}
}
int p=1;
for(int i=2;i<=n;i++){
if(dist[p]<dist[i]) p=i;
}
return p;
};
int L=bfs(1),R=bfs(L);
vector<int>ans(q+1,-1);
vector<int>path;
auto dfs=[&](auto self,int u,int fa)->void{
for(auto [i,k]:qry[u]){
if(k<=(int)path.size()){
ans[i]=path[(int)path.size()-k];
}
}
path.push_back(u);
for(auto v:E[u]){
if(v!=fa) self(self,v,u);
}
path.pop_back();
};
dfs(dfs,L,L);
dfs(dfs,R,R);
for(int i=1;i<=q;i++) cout<<ans[i]<<"\n";
return 0;
}
G.Increasing K Times (DP)
Problem
给定一个长度为\(N\)的序列\(A\),找到有多少个长度为\(N\)的排列\(P\),满足存在恰好\(K\)个整数\(i\)满足\(A_{P_i}\lt A_{P_{i+1}}\)
\(1\le N\le 5000\),\(0\le K\le N-1\),\(1\le A_i\le N\)
Solve
考虑DP,定义\(dp_{i,j}\)表示前\(i\)个数,有\(j\)个数满足条件的个数。考虑把\(A_i\)从小到大依次加入,假设当前加入了\(m\)个数\((a_1,a_2,\cdots,a_m)\),那么加入第\(m+1\)个数\(x\)的时候,要么$a_i\lt a_{i+1} \(的个数\)+1$,要么不变
- 如果是\(+1\)的情况,说明把\(x\)加入了满足\(x\gt a_i\ge a_{i+1}\)的\(a_i\)和\(a_{i+1}\)之间,对于有多少\(a_i\ge a_{i+1}\)可以通过有多少\(a_i\lt a_{i+1}\)来计算
- 如果是不变的情况,说明把前面\(a_i=x\)处且满足\(a_i\ge a_{i+1}\)的\(a_i\)替换成\(x\)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k;
cin>>n>>k;
k+=1;
vector<int>cnt(n+1);
for(int i=0;i<n;i++){
int x;
cin>>x;
cnt[x]++;
}
vector<ll>dp(k+1,0);
dp[0]=1;
int put=0,same=0;
for(int i=0;i<n;i++){
while(cnt[put]==0){
put+=1;
same=0;
}
vector<ll> nxt(k+1,0);
for(int j=0;j<=k;j++){
ll val=dp[j];
nxt[j]=(nxt[j]+val*(same+j)%mod)%mod; //不变
if(j<k)
nxt[j+1]=(nxt[j+1]+val*(i+1-j-same+mod)%mod)%mod; //增加
}
dp=move(nxt);
cnt[put]-=1;
same+=1;
}
cout<<dp[k]<<'\n';
return 0;
}
Ex - Odd Sum(生成函数、多项式)
Problem
给定一个长度为\(N\)的序列\(\{A\}\),问有多少种不同的方案,可以从这个序列中选择奇数个数使它们的和为\(M\)
\(1\le N\le 10^5\),\(1\le M\le 10^6\) \(1\le A_i\le 10\)
Solve
构造生成函数\(F_1=\prod_{i=1}^N(1+A_i)\),\(F_2=\prod_{i=1}^N(1-A_i)\)
\([x^M]F_1\)就表示多少种选数方案使得它们的和为\(M\),不考虑选数的奇偶性,即\(odd+even\)
\([x^M]F_2\)也表示多少种选数方案使得它们的和为\(M\),不过这里如果选数的个数是偶数,那么就加上,否则就减去,即\(even-odd\)
那么选数个数为奇数且和为\(M\)的方案数就是\(\frac{[x^M]F_1-[x^M]F_2}{2}\),即\(odd=\frac{odd+even-(even-odd)}{2}\)
Code
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 5e6+10;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809
//mod=950009857 g=7
const int mod = 998244353;
template <typename T>void read(T &x)
{
x = 0;
register int f = 1;
register char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
x *= f;
}
ll qpow(ll a, ll b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
namespace Poly
{
#define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
#define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了
typedef vector<ll> poly;
const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
//一般模数的原根为 2 3 5 7 10 6
const int inv_G = qpow(G, mod - 2);
int RR[N], deer[2][21][N], inv[N];
void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
for(int p = 1; p <= t; ++ p) {
int buf1 = qpow(G, (mod - 1) / (1 << p));
int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
deer[0][p][0] = deer[1][p][0] = 1;
for(int i = 1; i < (1 << p); ++ i) {
deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
}
}
inv[1] = 1;
for(int i = 2; i <= (1 << t); ++ i)
inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
}
inline int NTT_init(int n) {//快速数论变换预处理
int limit = 1, L = 0;
while(limit <= n) limit <<= 1, L ++ ;
for(int i = 0; i < limit; ++ i)
RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
return limit;
}
inline void NTT(poly &A, int type, int limit) {//快速数论变换
A.resize(limit);
for(int i = 0; i < limit; ++ i)
if(i < RR[i])
swap(A[i], A[RR[i]]);
for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
int len = mid >> 1;
for(int pos = 0; pos < limit; pos += mid) {
int *wn = deer[type][j];
for(int i = pos; i < pos + len; ++ i, ++ wn) {
int tmp = 1ll * (*wn) * A[i + len] % mod;
A[i + len] = ck(A[i] - tmp + mod);
A[i] = ck(A[i] + tmp);
}
}
}
if(type == 0) {
for(int i = 0; i < limit; ++ i)
A[i] = 1ll * A[i] * inv[limit] % mod;
}
}
inline poly poly_mul(poly A, poly B) {//多项式乘法
int deg = A.size() + B.size() - 1;
int limit = NTT_init(deg);
poly C(limit);
NTT(A, 1, limit);
NTT(B, 1, limit);
for(int i = 0; i < limit; ++ i)
C[i] = 1ll * A[i] * B[i] % mod;
NTT(C, 0, limit);
C.resize(deg);
return C;
}
//多项式牛顿迭代:求g(f(x))=0mod(x^n)中的f(x)
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
Poly::init(20);
int n,m;
cin>>n>>m;
vector<Poly::poly>f1(n+1),f2(n+1);
for(int i=1;i<=n;i++){
int x;
cin>>x;
f1[i].resize(x+1);
f2[i].resize(x+1);
f1[i][x]=1,f1[i][0]=1;
f2[i][x]=mod-1,f2[i][0]=1;
}
auto CDQ=[&](auto self,int l,int r,vector<Poly::poly>&f)->Poly::poly{
if(l==r) return f[l];
int mid=(l+r)>>1;
return Poly::poly_mul(self(self,l,mid,f),self(self,mid+1,r,f));
};
auto res1=CDQ(CDQ,1,n,f1);
auto res2=CDQ(CDQ,1,n,f2);
res1.resize(m+1);
res2.resize(m+1);
ll ans=1LL*(res1[m]-res2[m]+mod)%mod*qpow(2,mod-2)%mod;
cout<<ans<<'\n';
}