予鈴

アウトプットとメモ書きの中間みたいな記事がたくさん出ます。

CS Academy Array Removal

csacademy.com

問題概要

N要素の数列が与えられ、N要素のsubarrayが与えられる。
N 回連続する数列の和の最大値を出力する。ただし、subarrayの i番目の数字は使えなくなる。

解く前に考えてたこと

  • DP : 不可能そう。使えない数字が出るたびに引くと間に合わない
  • StarrySkyTree:区間がどれほど重複してるか数える必要が出てきて、無理となった。

解法

union-findとsubarrayを逆からみていくと良い。
i番目の数字を使えるとすると、i-1i+1番目の要素とuniteしていく。各集合のparentをkeyとする和の配列sumを持っていると良い。
答えとなりうるのは、i番目の要素を足す前の最大値か、足した後の和となるので、そのあたりはうまいことやる。

#include <bits/stdc++.h>
using ll = long long;
#define int ll
#define INF 1e9
#define EPS 0.0000000001
#define rep(i,n) for(int i=0;i<n;i++)
#define all(in) in.begin(), in.end()
template<class T, class S> void cmin(T &a, const S &b) { if (a > b)a = b; }
template<class T, class S> void cmax(T &a, const S &b) { if (a < b)a = b; }
using namespace std;
#define MAX 9999999
class unionfind {
    vector<int> par, rank, size_ ,sum;
public:
    unionfind(int n) :par(n), rank(n), size_(n, 1),sum(n,0) {
        iota(all(par), 0);
    }
    int find(int x) {
        if (par[x] == x)return x;
        return par[x] = find(par[x]);
    }
    void unite(int x, int y) {
        
        x = find(x), y = find(y);
        if (x == y)return;
        if (rank[x] < rank[y])swap(x, y);
        par[y] = x;
        size_[x] += size_[y];
        if (rank[x] == rank[y])rank[x]++;
    }
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    int size(int x) {
        return size_[find(x)];
    }
};
signed main(){
    int n, maxi = 0;
    cin >> n;
    vector<int> v(n,0),sub(n),ans,sum(2*n,0);
    vector<bool> used(n,false);
    unionfind uni(2*n);
    rep(i,n) cin >> v[i];
    rep(i,n) cin >> sub[i];
    rep(i,n) {
        uni.unite(i,i+n);
        sum[uni.find(i)] = v[i];
    }
    reverse(all(sub));
    rep(i,sub.size()){
        int index = sub[i]-1;
        maxi = max(maxi,v[index]);
        if(index+1 < n){
            if(used[index+1]){
                int val = sum[uni.find(index)]+sum[uni.find(index+1)];
                uni.unite(index, index+1);
                sum[uni.find(index)] =val;
            }
        }
        if(index-1 >=0){
            if(used[index-1]){
                int val = sum[uni.find(index)]+sum[uni.find(index-1)];
                uni.unite(index,index-1);
                sum[uni.find(index)] = val;
                
            }
        }
        cmax(maxi, sum[uni.find(index)]);
        
        ans.push_back(maxi);
        used[index] = true;
    }
    reverse(all(ans));
    rep(i,ans.size()) cout << ans[i] << endl;
}

RUPC2018 参加記

RUPC2018に参加しました。

Day 0

今回は作問チームに関わってもおらず、何も準備してません。ごめんなさい。

 

Day 1

家で寝てました。

Day2

立命館大学 プロジェクト団体 RiPP○が9時に来いって言われたので早起きした。

・久方ぶりにサークルに顔を出すと団体長がアになってた。

・ @tubo28さん、@ferin_tech15さん,@kjuner8さんとチームを組みました。

・A問題を解いて、その後はE問題をひたすら考えてた。到達可能回数を二分探+シミュレーションしようとしたものの、最善手が選べないことに気づきDijkstraをferinさんと実装するもバグが取れず。悲しかったし申し訳なかったです。

・懇親会にも参加しました。

下の写真はixmel先輩です。

 

Day3

・集合時間が8時30分は無理です。つらすぎる。

・大悪魔さん(@ukuuku09)といらいざさん(@Eliza_0x)とチームを組みました。

・B問題を通してDのDPをひたすら考えてたんですが、これまた場合分けとかがややこしい事になり通らずに悲しい結果に。

・大悪魔さんが最小カットを✝やるだけ✝といって爆速で通していてプロ。

・実装力を鍛えたいねと思ったコンテストでした

最後に

普段Twitter上で見る方々と実際にあえて楽しかったです。

今回は運営側にもかかわらずほんとに何もしていないので来年はそういったところでも貢献したいね。

あと強くなりたいと思いました。

D- People on a Line

D - People on a Line

コンテスト中に解けなくて悲しい思いをした問題。

コンテスト中に考えてたこと

・全点対最短路を求めることができたら良さそう。
・有効辺のみなので、dfs(コンテスト中はDijkstraをした)時にusedとdを持つと解ける。
(つまりdfsごとに到達と距離の配列を持つ必要がある)
・グラフは非連結の場合がある。↑の方針だとグラフの数だけ配列が必要になって解けそうにない。

正解のときに考えたこと

・1つの座標を決めることで、全ての座標が決まる。
・逆辺を貼ることで、一度のdfsで到達したか、どうかを判定する配列を持つ必要がない。
これトポロジカル順にソートすると逆辺はらなくてよくない?
・訪問した頂点の全ての辺について探索を行うので、再訪問の必要はない(よって間に合う。

トポロジカルソートでもうまいことやると解けるらしい。

#include<bits/stdc++.h>
using ll = long long;
#define int ll
#define INF 100000000000000LL
#define rep(i,n) for(int i=0;i<n;i++)
using namespace std;
using pii = pair<int,int>;
vector<bool>used;
vector<vector<pii>>edge;
vector<int>d;
bool judge=true;

void dfs(int v,int val){
    used[v]=true;
    rep(i,edge[v].size()){
        int next=edge[v][i].first;
        int cost=edge[v][i].second;
        if(d[next]==INF){
            d[next]=val+cost;
            if(!used[next])dfs(next,d[next]);
        }else if(d[next]!=cost+val){
            judge=false;
        }
    }
}
signed main(){
    int n,m; cin>>n>>m;
    
    edge = vector<vector<pii>>(n);
    used = vector<bool>(n,false);
    d    = vector<int>(n,INF);
    
    rep(i,m){
        int a,b,c;cin>>a>>b>>c;
        --a,--b;
        edge[a].push_back(make_pair(b,c));
        edge[b].push_back(make_pair(a,-c));
    }
    rep(i,n)if(!used[i])dfs(i,0);
    cout << (judge ? "Yes" : "No") << endl;
}

2019/6月追記
コードがひどすぎたので直した。

RiPProを休部する話

RiPProを休部することにしました。

※この記事は完全にポエムです。

理由をメンヘラにならないように簡潔に書く。

 

①留学の準備のために1日3,4時間ほど勉強する必要がでてきた。

留学のために2017年の前半を溶かしたといっても過言ではないので、留学はしっかり結果を残したい。そのためにも、準備をきちんとすることにした。

 

②競プロが辛い。

*1個人的に競プロの世界は弱肉強食だと思っているので、レートが上がらない、強くなれてる気がしないという状況に、そろそろ辛くなって来た。

精進が足りないと言われれば、それまでなんだけど、*2考えが変わるまで自分のペースでゆったり競プロをやることにした。

 

来年の4月までいません。

迷惑をかけるかもしれませんが、オネガイシマス。

*1:異論あると思う

*2:競プロは楽しいものだと思ってます

D-塗り絵

 
D: 塗り絵 - AtCoder Beginner Contest 036 | AtCoder

N個の島があり、N-1個の橋がある。
島を白または黒に塗っていく。ただし、両端の島を黒で塗ることはできない。
色の塗り方が何通りあるか求める問題です。

グラフ上の頂点を、黒色で塗るか、白色で塗るかをメモ化再帰で求めていきます。

memo[v][color]:=頂点vを  colorで塗ったときの場合の数

とします。(1が白、0が黒)


(1)頂点vを白色に塗るとき(uvの子の数)

vの子は白、黒の2つの色に塗ることができるので
memo[v][1] =( memo[nv_1][1]+ memo[nv_1][0])*\cdots*( memo[nv_u][1]+ memo[nv_u][0])


(2)頂点vを黒色に塗るとき

vの子は白色に塗ることができるので
memo[v][0] = ( memo[nv_1][1])*\cdots*( memo[nv_u][1]


ただし、 vが葉となる場合は1とします。

#include<bits/stdc++.h>
#define vi vector<int>
#define vvi vector<vector<int> >
#define vl vector<ll>
#define vvl vector<vector<ll>>
#define vb vector<bool>
#define vc vector<char>
#define vs vector<string>
using ll = long long;
using ld =long double;
#define int ll
#define INF 1e9
#define EPS 0.0000000001
#define rep(i,n) for(int i=0;i<n;i++)
#define loop(i,s,n) for(int i=s;i<n;i++)
#define all(in) in.begin(), in.end()
template<class T, class S> void cmin(T &a, const S &b) { if (a > b)a = b; }
template<class T, class S> void cmax(T &a, const S &b) { if (a < b)a = b; }
#define MAX 9999999
using namespace std;
typedef pair<int, int> pii;
typedef pair<int,pii> piii;
#define mp make_pair
int MOD = INF+7;
int memo[100005][2];
vector<vector<int>>edge;

int dfs(int v,int from,int clr){
    if(memo[v][clr]!=INF)return memo[v][clr];
    memo[v][clr]=0;
    bool leaf =  true;
    int value=1;
    rep(i,edge[v].size()){
        int nv=edge[v][i];
        if(nv == from)continue;
        leaf = false;
        if(clr){
            value*=(dfs(nv,v,0)+dfs(nv,v,1));
            value%= MOD;
        }else{
            value*=dfs(nv,v,1);
            value%=(MOD);
        }
    }
    if(leaf)memo[v][clr]=1;
    else memo[v][clr]=value;
    return memo[v][clr];
}
signed main(){
    int n;
    cin>>n;
    edge.resize(n);
    rep(i,n-1){
        int a,b; cin>>a>>b;
        --a,--b;
        edge[a].push_back(b);
        edge[b].push_back(a);
    }
    rep(i,n)rep(j,2)memo[i][j]=INF;
    cout<<(dfs(0,0,1)+dfs(0,0,0))%(MOD)<<endl;
    
}

D - Mixing Experiment

D: Mixing Experiment - AtCoder Beginner Contest 054 | AtCoder

薬品をM_a : M_bにする最小費用を求める問題。
制約も小さいので、 dp[n][ca][cb]でナップサックをすると解ける。
更新をca,cbを薬品の最小のだと思い込んでて時間を溶かした。
具体的には、漸化式を

 int div=(int)gcd((ll)(j+val[i+1].first),(ll)(k+val[i+1].second));
                pii ni=mp((j+val[i+1].first)/div,(k+val[i+1].second)/div);
                cmin(dp[i+1][ni.first][ni.second],dp[i][j][k]+c[i+1]);

こんな形で更新してた。
頭を冷やしてみると、比の足し算はやばい事がわかるし、比はcacbの間にしか成り立たない関係ということもわかる。
反省。

#include<bits/stdc++.h>
#define vi vector<int>
#define vvi vector<vector<int> >
#define vl vector<ll>
#define vvl vector<vector<ll>>
#define vb vector<bool>
#define vc vector<char>
#define vs vector<string>
using ll = long long;
using ld =long double;
#define int ll
#define INF 1e9
#define EPS 0.0000000001
#define rep(i,n) for(int i=0;i<n;i++)
#define loop(i,s,n) for(int i=s;i<n;i++)
#define all(in) in.begin(), in.end()
template<class T, class S> void cmin(T &a, const S &b) { if (a > b)a = b; }
template<class T, class S> void cmax(T &a, const S &b) { if (a < b)a = b; }
#define MAX 9999999
using namespace std;
typedef pair<int, int> pii;
typedef pair<int,pii> piii;
#define mp make_pair
long long gcd(long long a, long long b) {return b ? gcd(b, a % b) : a;}
long long lcm(long long m, long long n ){
    if((0 == m )||( 0 == n ))return 0;
    return ((m / gcd(m, n)) * n);
}
int dp[41][401][401];
signed main(){
    int n; cin>>n;
    pii comp; cin>>comp.first>>comp.second;
    rep(i,n)rep(j,401)rep(k,401)dp[i][j][k]=INF;
    dp[0][0][0]=0;
    vector<pii>val;
    vector<int>c(n);
    rep(i,n){
        pii temp; cin>>temp.first>>temp.second;
        cin>>c[i];
        val.push_back(mp(temp.first,temp.second));
        dp[i][val[i].first][val[i].second]=c[i];
    }
    rep(i,n-1){
        rep(j,401){
            rep(k,401){
                if(dp[i][j][k]==INF)continue;
                cmin(dp[i+1][j][k],dp[i][j][k]);
                pii ni=mp((j+val[i+1].first),(k+val[i+1].second));
                cmin(dp[i+1][ni.first][ni.second],dp[i][j][k]+c[i+1]);
            }
        }
    }
    int ans=INF;
    rep(i,401)rep(j,401){
        int div=gcd(i,j);
        if(i&&j){
        if(i/div==comp.first&&j/div==comp.second)cmin(ans,dp[n-1][i][j]);
        }
    }
    if(ans==INF)ans=-1;
    cout<<ans<<endl;
}

D - 3N Numbers

D: 3N Numbers - AtCoder Beginner Contest 062 | AtCoder


以前解けなかった問題が、ヒントで解けるようになったので…
前半の要素Nと後半の要素Nの差を最大にすれば良い。


中間の要素(配列の添字だとn \sim 2n-1)を最適に選べば良い。
PriorityQueueを使って、前半N要素のうちの最小値と中間N要素を比較していく。(後半の場合も同じく)

ちなみに、
Submission #1735282 - AtCoder Beginner Contest 062 | AtCoder
こういう解法だと絶対に解けない。N要素全部前半にくっつけたほうが良い場合がある。
N が奇数の時、前半と後半の両方に含まれる場合があるんじゃないかと思ってたけど、そんなことはない。
(↑これでめっちゃ時間とかした。)


#include<bits/stdc++.h>
using ll =  long long;
#define int ll
#define ld long double
#define INF 1e9
#define EPS 0.0000000001
#define rep(i,n) for(int i=0;i<n;i++)
#define all(in) in.begin(), in.end()
template<class T, class S> void cmin(T &a, const S &b) { if (a > b)a = b; }
template<class T, class S> void cmax(T &a, const S &b) { if (a < b)a = b; }
#define MAX 9999999
using namespace std;
typedef pair<int, int> pii;
typedef pair<double,double>pdd;
signed main(){
    int n; cin>>n;
    vector<int>v(3*n);
    rep(i,3*n) cin>>v[i];
    priority_queue<int,vector<int>,greater<int>>lque;
    priority_queue<int,vector<int>>sque;
    int ls=0,ss=0;
    vector<int>lv;
    vector<int>sv;
    rep(i,n){
        lque.push(v[i]);
        sque.push(v[2*n+i]);
        ls+=v[i];
        ss+=v[2*n+i];
    }
    lv.push_back(ls);
    sv.push_back(ss);
    int ans=-INF*INF;
    cmax(ans,ls-ss);
    rep(i,n){
        lque.push(v[n+i]);
        sque.push(v[2*n-i-1]);
        ls+=v[n+i]-lque.top();
        ss+=v[2*n-i-1]-sque.top();
        lv.push_back(ls);
        sv.push_back(ss);
        sque.pop();
        lque.pop();
    }
    rep(i,lv.size())cmax(ans, lv[i]-sv[n-i]);
    cout<<ans<<endl;
}
 

lv[i] := i番目まで見たときの、前半の合計の最大値
sv[i]:= i番目まで見たときの、後半の合計の最小値
添字マジックには気をつけよう!