https://atcoder.jp/contests/abc368
D
一眼虚树,对着关键点把虚树建出来然后求一下虚树上的点数(?),这个东西我认为是可以在构建虚树的时候通过树上距离求出来的,如果多组询问然后保证询问的总关键点个数,那么这个做法就是无敌了。
你说得对,但是 ABC D 上虚树还是夸张了一点哦。进一步观察这个包含关键点的最小子图,会发现这个子图的每个叶子都是关键点(无根树中,度数为 $1$ 的节点我们称作叶子)。我们容易想到钦定一个关键点 $v_1$ 为树根,那么我们从 $v_1$ 开始往原树 dfs,那么就可以变成一个删子树的过程。如果一个子树 $sub(u)$ 内没有关键点,那么我们就可以删掉这个子树。所以我们在 dfs 的过程中删掉所有不包含任何关键点的极大子树就好了。
更为简单的想法是考虑哪些点需要保留,这样一遍 dfs 就够了。如果一个点的子树里面有被打了标记的点,那么这个点就得保留。
Fysty
#include <bits/stdc++.h>
#include <random>
#include <chrono>
using namespace std;
//#pragma GCC optimize("Ofast")
//#pragma GCC optimize ("unroll-loops")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<double,double> pdd;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
template<typename T> void _do(T x){cerr<<x<<"\n";}
template<typename T,typename ...U> void _do(T x,U ...y){cerr<<x<<", ";_do(y...);}
#define dbg(...) cerr<<#__VA_ARGS__<<" = ";_do(__VA_ARGS__);
const int MOD1=1e9+7;
const int MOD2=998244353;
const ll INF=4e18;
ll fpow(ll a,ll b,ll m)
{
if(!b) return 1;
ll tmp=1;
for(ll cur=a;b;b>>=1,cur=cur*cur%m) if(b&1) tmp=tmp*cur%m;
return tmp;
}
ll inv(ll a,ll m) {return fpow(a,m-2,m);}
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,n) for(int i=0;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define F first
#define S second
#define pb push_back
#define uni(c) c.resize(distance(c.begin(),unique(c.begin(),c.end())))
#define unisort(c) sort(c.begin(),c.end()),uni(c)
const int N=2e5+5;
vector<int> ed[N];
bool need[N];
int ans=0;
void dfs(int u,int p)
{
for(int v:ed[u]) if(v!=p)
{
dfs(v,u);
need[u]|=need[v];
}
if(need[u]) ans++;
}
void solve()
{
int n,k;
cin>>n>>k;
rep(i,n-1)
{
int u,v;
cin>>u>>v;
ed[u].pb(v);
ed[v].pb(u);
}
vector<int> a(k);
rep(i,k)
{
cin>>a[i];
need[a[i]]=1;
}
dfs(a[0],0);
cout<<ans<<"\n";
}
signed main()
{
MottoHayaku
int t;
//cin>>t;
t=1;
while(t--)
{
solve();
}
}
linear 提出了一个另解,虽然和上面的做法本质相同,但是我觉得它更接近本质也很有启发性。重新分析“这个子图的每个叶子都是关键点”这条性质。我们可以直接一点地刻画这条性质:还是采用删点的构造方式,将当前的叶子集合维护出来,每次删掉非关键点的叶子,然后它的邻点又会成为新的叶子,把它放入这个叶子集合。这样的话,每个点最多进入叶子集合一次,被 check 一次,只有 $\mathcal{O}(n)$ 次判断。用 set 来维护这个集合即可。
其实这个做法和 prufer 序列的维护很像的,所以我觉得很有启发性啊!拜谢 linear 被 D 硬控 27min。
yyc:信息学的未来属于 linear!
int WITHERING([[maybe_unused]] unsigned long long int TEST_NUMBER) {
tp n, k; cin >> n >> k;
vector<set<tp>> e(n + 1);
vetp l;
for (tp i = 2; i <= n; ++i) {
tp u, v; cin >> u >> v;
e[u].insert(v);
e[v].insert(u);
}
for (tp i = 1; i <= n; ++i) {
if (e[i].size() == 1) l.push_back(i);
}
set<tp> v;
while (k--) {
tp x; cin >> x;
v.insert(x);
}
tp tar = 0;
while (l.size()) {
vetp r;
for (auto& i : l) {
if (!v.count(i)) {
++tar;
e[*e[i].begin()].erase(i);
if (e[*e[i].begin()].size() == 1) r.push_back(*e[i].begin());
} else v.erase(i);
}
l.swap(r);
}
cout << n - tar << '\n';
return 0;
}
void MIST() {
}
struct STRUGGLE {
STRUGGLE() {
cin.tie(nullptr)->sync_with_stdio(false);
}
} STRUGGLE;
// :\ */
E
ABC E 特有的脑筋急转弯题。
容易想到一个贪心,就是按照最短路来走,但是这个贪心是不对的,因为一条边可能受多个不同结束时间顺序的点控制。这启发我们按照发生的相对时间来处理这个问题。
考虑维护 $f_u$ 表示到达节点 $u$ 后最晚的下一轮开始时间。那么我们按照每条边 $(u_i,v_i,s_i,t_i)$ 的开始时间,结束时间将其拆为两个事件 $(s_i,i),(t_i,-i)$。考虑按照时间顺序依次考虑这些事件:
- 如果碰到一个 $(s_i,i)$ 事件,那么将 $x_i\gets f_u-s_i$。
- 如果碰到一个 $(t_i,-i)$ 事件,那么就是说 $i$ 这条边已经确定好了,所以 $f_u\gets t_i+x_i$。
其中这里的 $\gets$ 表示 checkmax。
void FALLING() {
int n, m; cin >> n >> m;
V<int> t(n + 1, 0), ans(m + 1, 0);
cin >> ans[1];
V<int> u(m + 1, 0), v(m + 1, 0);
V<pi> e;
FOR (i, 1, m) {
int x, y; cin >> u[i] >> v[i] >> x >> y;
e.eb(x, i), e.eb(y, -i);
}
std::sort(ALL(e));
for (auto [x, i] : e) {
if (i > 0) {
if (i ^ 1) ans[i] = std::max(0, t[u[i]] - x);
}
else i = -i, ckmax(t[v[i]], ans[i] + x);
}
FOR (i, 2, m) cout << ans[i] << ' ';
cout << "\n";
return ;
}
F
一眼丁真 CF2004E,补了那题的话这题就是很简单了!但是由于实现的时候脑抽了被硬控了 30min,从此彻底输掉。
容易想到求出每个数的 SG 函数值,对于每个数 $i$,考虑枚举它的所有约数 $d$,那么 $SG(i)=\text{mex}\{SG(d)\}$,我们可以直接暴力枚举 $d$,因为枚举 $d$ 实际上就是 $\sqrt i$ 级别的。问题在于怎么求这个 $\text{mex}$。
实际上有很多种实现方式。
注意到 $d$ 只有根号种,直接暴力打标记,然后从小到大枚举找到第一个没有标记的数,这样是可以保证根号内的,这是因为最多 ban 掉根号个数,其实我们就有一个性质了:$SG(i)=\mathcal{O}(\sqrt i)$。
qiuzx_
//ANMHLIJKTJIY! #pragma GCC optimize(2) #pragma GCC optimize("Ofast") #pragma GCC optimize("inline","fast-math","unroll-loops","no-stack-protector") #pragma GCC diagnostic error "-fwhole-program" #pragma GCC diagnostic error "-fcse-skip-blocks" #pragma GCC diagnostic error "-funsafe-loop-optimizations" #include <bits/stdc++.h> #define INF 1000000000 #define LINF 1000000000000000000 #define MOD 1000000007 #define mod 998244353 #define F first #define S second #define ll long long #define N 100010 using namespace std; ll n,f[N]; vector<ll> fac[N]; bool vis[N]; int main(){ ll i,j; for(i=1;i<N;i++) { for(j=i+i;j<N;j+=i) { fac[j].push_back(i); } } for(i=2;i<N;i++) { for(j=0;j<fac[i].size();j++) { vis[f[fac[i][j]]]=true; } for(f[i]=0;vis[f[i]];f[i]++); for(j=0;j<fac[i].size();j++) { vis[f[fac[i][j]]]=false; } } scanf("%lld",&n); ll ans=0; for(i=0;i<n;i++) { scanf("%lld",&j); ans^=f[j]; } puts(ans?"Anna":"Bruno"); return 0; }
可以用一个 set 维护没有被打标记的值,这样会多一个 $\log n$,本质和上面的实现是一样的。
void FALLING() { int n, m = 0; cin >> n; V<int> a(n + 1, 0); FOR (i, 1, n) cin >> a[i], ckmax(m, a[i]); V<int> sg(m + 2, 0); std::set<int> s; FOR (i, 1, m + 1) s.insert(i); FOR (k, 2, m) { int sq = std::sqrt(k); V<int> d; FOR (i, 2, sq) { if (k % i) continue; d.eb(sg[i]); if (i * i != k) d.eb(sg[k / i]); } for (auto i : d) if (s.find(i) != end(s)) s.erase(i); sg[k] = *begin(s); for (auto i : d) s.insert(i); } int w = 0; FOR (i, 1, n) w ^= sg[a[i]]; cout << (w == 0 ? "Bruno\n" : "Anna\n"); return ; }
观察到最终的 SG 值实际上是 $\le \log_2 V$ 的,其中 $V$ 是数的值域,所以可以直接用类似于状压 dp 一样转移,记录每个数的后继中每种 SG 值是否存在了。这个做法需要较强的观察能力。
kotatsugame
#include<iostream> #include<set> #include<cassert> using namespace std; int Gru[1<<17]; int F[1<<17]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); for(int a=1;a<=(int)1e5;a++) { while(F[a]>>Gru[a]&1)Gru[a]++; for(int v=a+a;v<=(int)1e5;v+=a)F[v]|=1<<Gru[a]; } int N;cin>>N; int X=0; for(;N--;) { int a;cin>>a; X^=Gru[a]; } cout<<(X?"Anna":"Bruno")<<endl; }
G
It is guaranteed that the answers to the given type $3$ queries are at most $10^{18}$.
所以说,在询问的区间内,$b_i\ge 2$ 的 $i$ 至多有 $\log_2 10^{18}=60$ 个,除去这些,都是 $b_i=1$,那么肯定选择 $v+a_i$ 更优。所以用一个 set,维护所有 $b_i\ge 2$ 的点,然后找出 $[l,r]$ 内的这些点,相当于把 $[l,r]$ 分段了,点之间的段肯定都是 $+a_i$,所以维护一下 $a$ 的区间和就好了。单点修改 $a$,查询 $a$ 的区间和,差分一下维护前缀和,用树状数组就可以维护了。
kotatsugame
#include<iostream>
#include<set>
#include<cassert>
#include<atcoder/fenwicktree>
using namespace std;
int N,Q;
int A[1<<17],B[1<<17];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>N;
atcoder::fenwick_tree<long>BIT(N);
for(int i=0;i<N;i++)
{
cin>>A[i];
BIT.add(i,A[i]);
}
set<int>NO;
for(int i=0;i<N;i++)
{
cin>>B[i];
if(B[i]>1)NO.insert(i);
}
cin>>Q;
for(;Q--;)
{
int t;cin>>t;
if(t==1)
{
int i,x;cin>>i>>x;i--;
BIT.add(i,-A[i]);
BIT.add(i,x);
A[i]=x;
}
else if(t==2)
{
int i,x;cin>>i>>x;i--;
if(B[i]>1)NO.erase(i);
B[i]=x;
if(B[i]>1)NO.insert(i);
}
else
{
int l,r;cin>>l>>r;l--;
long v=A[l];
l++;
auto it=NO.lower_bound(l);
while(it!=NO.end()&&*it<r)
{
v+=BIT.sum(l,*it);
v=max(v+A[*it],v*B[*it]);
l=*it+1;
it++;
}
cout<<v+BIT.sum(l,r)<<"\n";
}
}
}
SSRS
#include <bits/stdc++.h>
using namespace std;
struct binary_indexed_tree{
int N;
vector<long long> BIT;
binary_indexed_tree(vector<int> A){
N = A.size();
BIT.resize(N + 1, 0);
for (int i = 0; i < N; i++){
BIT[i + 1] = A[i];
}
for (int i = 1; i < N; i++){
if (i + (i & -i) <= N){
BIT[i + (i & -i)] += BIT[i];
}
}
}
void add(int i, long long x){
i++;
while (i <= N){
BIT[i] += x;
i += i & -i;
}
}
long long sum(int i){
long long ans = 0;
while (i > 0){
ans += BIT[i];
i -= i & -i;
}
return ans;
}
long long sum(int L, int R){
return sum(R) - sum(L);
}
};
int main(){
int N;
cin >> N;
vector<int> A(N);
for (int i = 0; i < N; i++){
cin >> A[i];
}
vector<int> B(N);
for (int i = 0; i < N; i++){
cin >> B[i];
}
binary_indexed_tree BIT(A);
set<int> st;
for (int i = 0; i < N; i++){
if (B[i] != 1){
st.insert(i);
}
}
st.insert(N);
int Q;
cin >> Q;
for (int j = 0; j < Q; j++){\
int t;
cin >> t;
if (t == 1){
int i, x;
cin >> i >> x;
i--;
BIT.add(i, -A[i]);
A[i] = x;
BIT.add(i, A[i]);
}
if (t == 2){
int i, x;
cin >> i >> x;
i--;
if (B[i] != 1){
st.erase(i);
}
B[i] = x;
if (B[i] != 1){
st.insert(i);
}
}
if (t == 3){
int l, r;
cin >> l >> r;
l--;
auto itr = st.lower_bound(l);
long long v = 0;
int p = l;
while (p < r){
int nxt = min(*itr, r);
v += BIT.sum(p, nxt);
p = nxt;
if (p != r){
v = max(v + A[p], v * B[p]);
p++;
itr++;
}
}
cout << v << endl;
}
}
}
orz