halcの競プロ精進ブログ

はるくが競プロしたことを書き留めるなにか

【今日の精進】ABC163F - path pass i

毎日格上の問題を倒すやつの93日目です。

問題リンク

atcoder.jp

問題概要

頂点に色がついた木があるよ。

各色について、その色を一回以上通るパスの個数を求めてね。

解法

頂点0を根とした根つき木とします。

トポロジカルソートをして、オイラーツアー順を取得しておきます。これはどちらもDFSで対応できます。

各色、ソート順が大きい方から、「その頂点を通って、以前に処理した頂点たちは通らないパスの総数」を求めたいです。

これは、先ほど作ったオイラーツアーから遅延セグメント木を作成すれば、部分木の削除と部分木の頂点数の取得を高速にできます。

よって、これらからいい感じに計算すればよいです。

提出コード

#include<bits/stdc++.h>
#include<atcoder/lazysegtree>
using namespace std;
using namespace atcoder;
using ll = long long;
#define rep(i,n) for(int i=0; i<n; i++)
#pragma GCC target("avx2")
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")

void dfs(int pos,int bef,int &cnt,
        vector<int> &c,vector<vector<int>> &tree,vector<vector<int>> &col,vector<pair<int,int>> &tour){
    tour[pos].first=cnt;
    cnt++;
    col[c[pos]-1].push_back(pos);
    for(int i:tree[pos]){
        if(i!=bef){
            dfs(i,pos,cnt,c,tree,col,tour);
        }
    }
    tour[pos].second=cnt;
    cnt++;
}

pair<int,int> op(pair<int,int> x,pair<int,int> y){
    return pair<int,int>(x.first+y.first,x.second+y.second);
}

pair<int,int> e(){
    return pair<int,int>(0,0);
}

pair<int,int> mapp(int f,pair<int,int> x){
    if(f==-1)return x;
    return pair<int,int>(x.second*f,x.second);
}

int comp(int f,int g){
    if(f==-1)return g;
    return f;
}

int id(){
    return -1;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int N;
    cin>>N;
    vector<int> c(N);
    rep(i,N){
        cin>>c[i];
    }
    vector<vector<int>> tree(N);
    rep(i,N-1){
        int a,b;
        cin>>a>>b;
        tree[a-1].push_back(b-1);
        tree[b-1].push_back(a-1);
    }
    vector<vector<int>> col(N);
    vector<pair<int,int>> tour(N);
    int cnt=0;
    dfs(0,-1,cnt,c,tree,col,tour);
    lazy_segtree<pair<int,int>,op,e,int,mapp,comp,id> seg(vector<pair<int,int>>(N*2,pair<int,int>(1,1)));
    rep(i,N){
        seg.apply(0,N*2,1);
        reverse(col[i].begin(),col[i].end());
        ll ans=0;
        for(int j:col[i]){
            ll add=0,squ=0;
            for(int k:tree[j]){
                if(tour[j].first<tour[k].first){
                    ll size=seg.prod(tour[k].first,tour[k].second+1).first/2;
                    add+=size;
                    squ+=size*size;
                }
            }
            ll size=(seg.all_prod().first-seg.prod(tour[j].first,tour[j].second+1).first)/2;
            add+=size;
            squ+=size*size;
            ans+=(add*add-squ)/2+seg.all_prod().first/2;
            seg.apply(tour[j].first,tour[j].second+1,0);
        }
        cout<<ans<<endl;
    }
}

atcoder.jp

感想

オイラーツアーだけでなくいろんな典型が使えるいい問題でした

【今日の精進】ARC114C - Sequence Scores

毎日格上の問題を倒すやつの92日目です。

問題リンク

atcoder.jp

問題概要

長さが N で要素が 1 以上 M 以下である数列すべてについて、以下の値の和を 998244353 で割ったあまりを求めてね。

  • 長さが N で要素がすべて 0 である数列から、「ある区間の数をその数と決めた数の大きい方で置き換える」ことを繰り返したとき、数列を作る最小の操作数

解法

ある数を右に追加して、操作回数が増えるかどうかを考えます。

操作回数が増えない条件は、それより左が

  • その数より真に大きいがいくつか
  • その数が1つ
  • その数以下がいくつか

という数列になっているときです。これはDPで差分を計算できます。

提出コード

MOD=998244353
N,M=map(int,input().split())
dp=[0]*M
powm=[1]*(N+1)
for i in range(N):
    powm[i+1]=(powm[i]*M)%MOD
ans=0
for i in range(N):
    for j in range(M):
        ans+=powm[N-1]-dp[j]*powm[N-i-1]
        dp[j]*=(M-j-1)
        dp[j]+=powm[i]
        dp[j]%=MOD
    ans%=MOD
print(ans)

Submission #48683723 - AtCoder Regular Contest 114

感想

この回出てたら2500パフォくらい出てたらしく、涙…*1

*1:Eも解けてるので

【今日の精進】ABC195F - Coprime Present

毎日格上の問題を倒すやつの91日目です。

問題リンク

atcoder.jp

問題概要

A 以上 B 以下のすべての整数の部分集合であって、どの二つも互いに素であるようなものの個数を求めてね。

解法

幅が狭いのが意味深すぎます。

それぞれの整数について、共通の素数で割れなければよくて、その素数72 以下を考えればよいです。

よって、それらすべてについて、すでに使ったかをもってbitDPすればよいです。

提出コード

A,B=map(int,input().split())
prime=[]
for i in range(2,72):
    if i%2!=0 or i==2:
        if i%3!=0 or i==3:
            if i%5!=0 or i==5:
                if i%7!=0 or i==7:
                    prime.append(i)
n=len(prime)
dp=[0]*(1<<n)
dp[0]=1
for i in range(A,B+1):
    bit=0
    for j,k in enumerate(prime):
        if i%k==0:
            bit+=1<<j
    for j in range(1<<n):
        if j&bit==0:
            dp[j|bit]+=dp[j]
print(sum(dp))

感想

最初包除原理とかで迷走したな…

【今日の精進】AGC005F - Many Easy Problems

毎日格上の問題を倒すやつの90日目です。

問題リンク

atcoder.jp

問題概要

以下のような問題を考えるよ。

整数 N,KN 頂点の木がある。

頂点を K 個選ぶ方法は _N\text{C}_K 通りあるが、それぞれの選び方に対して、「選んだ頂点すべてを含む最小の全域木の頂点数」をスコアとする。

全ての選び方に対するスコアの合計を 924844033 で割った余りを求めよ。

この問題を一瞬で解いた高橋くんは、K=1,2\dots,N のすべての場合について高速に答えを求める方法を考えることにしたよ。

実際に求めてね。

解法

まず、K が固定されたものを考えます。しかし、これも少し難しいです。

直接求めるのは難しいので、余事象を考えます。というか、「全域木に含まれない頂点数」をスコアにします。これを元に戻すのは容易です。

スコアの対象を変えた後の問題で、各頂点のスコアへの寄与を考えます。これは、その頂点を取り除いたときにできる部分木で、そのうち1つにのみ選んだ頂点が集中しているときに加算されます。

よって、木DPして各頂点ごとに求めれば良いです。後のために、部分木の大きさをすべてメモしておき、頂点数 i の部分木の個数が a_i であるような整数列 a を作っておきます。

K を固定した時の(スコアを言い換えた)答えを改めて考えると、\sum_{i=0}^{N}a_i\times_i\text{C}_K となります。二項定理より、これは [x^K]\sum_{i=0}^{N}a_i\times(x+1)^i となります。

ここまで来れば後一息で、f(x)=\sum_{i=0}^{N}a_i\times x^i としたときの f(x+1) を求めたいので、これは「Polynomial Taylor Shift」に学ぶ畳み込み芸 - けんちょんの競プロ精進記録をそのまま適用できます。

提出コード

#include<bits/stdc++.h>
#include<atcoder/all>
using namespace std;
using namespace atcoder;
using mint = static_modint<924844033>;
using ll = long long;
#define rep(i,n) for(int i=0; i<n; i++)

int dfs(int pos,int bef,int &N,vector<vector<int>> &tree,vector<mint> &dp){
    int ret=1;
    for(int i:tree[pos]){
        if(bef==i)continue;
        int now=dfs(i,pos,N,tree,dp);
        dp[now]++;
        ret+=now;
    }
    dp[N-ret]++;
    return ret;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int N;
    cin>>N;
    vector<vector<int>> tree(N);
    rep(i,N-1){
        int u,v;
        cin>>u>>v;
        tree[u-1].push_back(v-1);
        tree[v-1].push_back(u-1);
    }
    vector<mint> dp(N+1,0),fact(N+1,1),rev(N+1,1);
    dfs(0,-1,N,tree,dp);
    rep(i,N){
        fact[i+1]=fact[i]*(i+1);
        rev[i+1]=rev[i]/(i+1);
    }
    rep(i,N+1){
        dp[i]*=fact[i];
    }
    vector<mint> pol(N+1);
    rep(i,N+1){
        pol[i]=rev[N-i];
    }
    vector<mint> shift=convolution(pol,dp);
    rep(i,N+1){
        if(i==0)continue;
        cout<<(fact[N]*rev[i]*rev[N-i]*N-shift[i+N]*rev[i]).val()<<"\n";
    }
}

Submission #48649270 - AtCoder Grand Contest 005

感想

最後の最後、辺を有向にしてめっちゃ落としたのは私です

【今日の精進】ABC283G - Partial Xor Enumeration

毎日格上の問題を倒すやつの89日目です。

問題リンク

atcoder.jp

問題概要

数列 A のいくつかの要素をxorしてできる数のうち、小さい方から L 番目から R 番目までのものを列挙してね。

解法

xorの基底の練習問題です。

noshiさんが広めたよくわからないすごい方法で基底を求めます。

すると、あとは上の位から決定すればよいです。

提出コード

N,L,R=map(int,input().split())
base=[]
for i in list(map(int,input().split())):
    for j in base:
        i=min(i,i^j)
    if i:
        base.append(i)
base.sort()
ans=[]
for i in range(L-1,R):
    ans.append(0)
    for j in range(len(base)-1,-1,-1):
        if (i>>j)&1:
            ans[-1]=max(ans[-1],ans[-1]^base[j])
        else:
            ans[-1]=min(ans[-1],ans[-1]^base[j])
print(*ans)

Submission #48629696 - UNIQUE VISION Programming Contest 2022 Winter(AtCoder Beginner Contest 283)

感想

あまりにもうまくいっててびっくりした

空いた日にやってたこと

87日目

88日目

【今日の精進】M-SOLUTIONS2019E - Product of Arithmetic Progression

毎日格上の問題を倒すやつの86日目です。

問題リンク

atcoder.jp

問題概要

x,x+d,\dots,x+d(n-1) の積を 10^7+3 で割ったあまりを求めてね。

解法

x=0d=0 の時は先に処理します。これはどちらも容易です。

x=d の場合は非常に簡単です。階乗を前計算して、d^n 倍すればよいです。

一般の場合でも、逆数でいい感じに計算すれば同じ状況のものが二つになります。なので割り算すればよいです。

提出コード

MOD=1000003
fact=[1]*(MOD+1)
for i in range(1,MOD+1):
    fact[i]=(fact[i-1]*i)%MOD
Q=int(input())
for _ in range(Q):
    x,d,n=map(int,input().split())
    if x==0:
        print(0)
        continue
    if d==0:
        print(pow(x,n,MOD))
        continue
    pos=(x*pow(d,-1,MOD)%MOD)
    if pos+n-1>=MOD:
        print(0)
        continue
    print(pow(d,n,MOD)*fact[pos+n-1]*pow(fact[pos-1],-1,MOD)%MOD)

Submission #48499217 - M-SOLUTIONS Programming Contest

解法

久しぶりにらくちんでした

【今日の精進】AGC038C - LCMs

毎日格上の問題を倒すやつの85日目です。

問題リンク

atcoder.jp

問題概要

数列 A があるよ。

A のすべてのペアの最小公倍数の和を求めてね。

解法

いわゆる約数包除です。

最大公約数が i のペアを考えると、数の種類が少ないおかげでいい感じに計算できます。

それぞれのペアの積の合計を求めたいですが、これは和と二乗和を持っておけば良いです。

二乗に見えますが、(弱い)エラトステネスの篩と同じ要領でlogがつく程度で済みます。

提出コード

MAX_A=10**6
MOD=998244353
rev2=pow(2,-1,MOD)
N=int(input())
A=list(map(int,input().split()))
cnt=[0]*(MAX_A+1)
for i in A:
    cnt[i]+=1
div=[0]*(MAX_A+1)
ans=0
for i in range(1,MAX_A+1):
    use=pow(i,-1,MOD)-div[i]
    add=0
    squ=0
    for j in range(i,MAX_A+1,i):
        squ+=j*j*cnt[j]
        add+=j*cnt[j]
    ans+=use*((add*add-squ)*rev2)
    ans%=MOD
    for j in range(i,MAX_A+1,i):
        div[j]+=use
print(ans)

Submission #48481937 - AtCoder Grand Contest 038

感想

くりさんの問題もりーふさんの問題くらい引くようになってきたな