2021年11月30日 星期二

[ZJ e156] 良心題: 求和

原題: e156. 良心題: 求和 - 高中生程式解題系統 (zerojudge.tw)


如何不用乘法、除法、<<、>>、~、^,也不用for、while、if、else、switch、case及三元運算子,算出1+2+3+...+n ?


解題方向:


我覺得這一題滿有趣的,分享一些解法,不同寫法用到的語法細節都有點不一樣


解法如下

解 1 : 利用 new 會呼叫建構式來實現類似 for 迴圈的結構

用 new A[N] ,來呼叫 N 次 A 的建構式,再利用變數控制加總即可。


#include <bits/stdc++.h>
using namespace std;

class A {
public:
    A() {
        sum+=cnt++;
    }
    inline static int cnt = 1;
    inline static int sum = 0;
};


int main() {
    int N;
    scanf("%d", &N);
    delete [] new A[N];
    printf("%d\n", A::sum);
}


解 2 : 利用 && 會短路 (shortcut) 的特性來實現 if


#include <bits/stdc++.h>
using namespace std;

int sum(int x) {
    x && (x+=sum(x-1));
    return x;
}

int main() {
    int N;
    scanf("%d", &N);
    printf("%d\n", sum(N));
}


解 3 : 其實用 STL ,加上 algorithm 的函數就兜得出來了


#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    int N;
    scanf("%d", &N);

    vector<int> v(N);
    iota(v.begin(), v.end(), 1);
    printf("%d\n", accumulate(v.begin(), v.end(), 0));
}

2021年11月25日 星期四

[ZJ b936] b936: Kevin 愛橘子

 原題: b936. Kevin 愛橘子 - 高中生程式解題系統 (zerojudge.tw)


解題方向:觀察


題目要求把 $n$ 不斷剖半 ( 遇到奇數就分成 $x,x+1$ ),問至少能分成幾塊,使每一塊大小至少為 $m$ 。


假設 $n \geq m$:

很顯然的,如果 $n=m\times 2^{k}$ ,答案為 $2^{k}$ 。

因此,我們要來討論 $n=m\times 2^{k}+r$,的情形,當中 $1\leq r < m\times 2^{k}$ 


根據觀察可以發現,每當 $r$ 增加 $2^{k}$ ,所有最後切下的部位就會增加 $1$:

比如說 

$n=25,m=6$ 時,會得到切除的結果是 $\{7,6,6,6\}, r=1$

$n=26,m=6$ 時,會得到切除的結果是 $\{7,7,6,6\}, r=2$

$n=27,m=6$ 時,會得到切除的結果是 $\{7,7,7,6\}, r=3$

$n=28,m=6$ 時,會得到切除的結果是 $\{7,7,7,7\}, r=4$

$n=29,m=6$ 時,會得到切除的結果是 $\{8,7,7,7\}, r=5$

因此,我們可以發現,當 $r\leq (m-1)\times 2^k$ 時,不影響答案,但是當 $r$ 大於 $ (m-1)\times 2^k$ 時,就會讓增加的項可以被近一步分割 !


比如說 

$n=43,m=6$ 時,會得到切除的結果是 $\{11,11,11,10\}, r=19$

$n=44,m=6$ 時,會得到切除的結果是 $\{11,11,11,11\}, r=20$

$n=45,m=6$ 時,會得到切除的結果是 $\{6,6,11,11,11\}, r=21$

原本是 $12$的項能被近一步分解為 $6,6$,因此分割數加了。


故我們可以得到下公式

$n = m\times 2^{k}+r$

$f(n)=\begin{cases}2^k&0\leq r\leq (m-1)\times 2^k\\2^k+t&(m-1)\times 2^k+t=r& t>0\end{cases}$


故得證。


#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    ll n, m;
    while (~scanf("%lld%lld", &n, &m)) {
        if (n<m) {
            puts("0");
            continue;
        }

        long long P = 0;
        for(int i=0;n>=m*(1LL<<i);i++) {
            P = i;
        }
        long long R = n - m*(1LL<<P);

        long long ans = 1LL<<P;
        if (R > (m-1) * (1LL<<P))
            ans += R - (m-1) * (1LL<<P);

        printf("%lld\n", ans);

    }
    return 0;
}

2021年11月23日 星期二

[ZJ a134][UVA 00948] Fibonaccimal Base

原題: a134. 00948 - Fibonaccimal Base - 高中生程式解題系統 (zerojudge.tw)


解題方向:觀察、進位制


根據觀察,如果我們把一個數字去盡可能大的費式數,就能構造題目要求沒有連續 $1$ 的答案。

證明:


設 $x$ 足夠大,能被兩個相鄰的費式數夾擠,也就是 $F_{n+1}>x \geq F_{n}$。

如果要有不相鄰的 $1$ ,那麼 $x-F_{n}$ 就要小於 $F_{n-1}$。


根據定義

$F_{n+1}=F_{n}+F_{n-1} > x$

移項

$F_{n-1} > x - F_{n}$


故得證。


#include<bits/stdc++.h>
using namespace std;

int main() {
    long long f[40] = {1, 2, 0};
    for(int i=2;i<40;++i)
        f[i] = f[i-1] + f[i-2];
    reverse(f, f+40);

    int N, x;
    cin >> N;
    while (N--) {
        cin >> x;
        cout << x << " = ";

        bool leadZero = true;
        for(long long v:f) {
            if (x >= v) {
                leadZero = false;
                cout << 1;
                x-=v;
            } else {
                if (!leadZero) cout << 0;
            }
        }

        cout << " (fib)\n";
    }
    return 0;
}

2015年10月25日 星期日

[TIOJ 1869] 堆石子遊戲

原題:http://tioj.ck.tp.edu.tw/problems/1869
AC:http://tioj.ck.tp.edu.tw/submissions/25991

解題方向:二維BIT,分塊


題目大意:平面點修改值,平面查詢總和。

解法:二維BIT可以在$O(Q\log^2N)$輕鬆過這一題,如果要練$\sqrt N$分塊的話也是可以。

#include<bits/stdc++.h>
using namespace std;
int bbit[1025][1025];
int N;
inline void add(int v,int x,int y)
{
    while( x <= N )
    {
        for(int j=y;j<=N;j+=j&-j)
            bbit[x][j]+=v;
        x+=x&-x;
    }
}
inline int sum(int x,int y)
{
    int ans = 0;
    while( x )
    {        
        for(int j=y;j;j-=j&-j)
            ans+=bbit[x][j];
        x-=x&-x;
    }
    return ans;
}
int main()
{
    cin>>N;
    int mod,x1,y1,x2,y2,v;
    while(cin>>mod)
    {
        if( mod == 1 )
        {
            cin>>x1>>y1>>v;
            x1++;y1++;
            add(v,x1,y1);
        }
        else
        {
            cin>>x1>>y1>>x2>>y2;
            x1++;y1++;x2++;y2++;
            cout<< sum(x2,y2) - sum(x1-1,y2) - sum(x2,y1-1)+sum(x1-1,y1-1) <<'\n';
        }
    }
}

2015年9月16日 星期三

台南一中104學年度第1學期資訊學科能力競賽校內初選 題解

20150916


Allen的歸來



難度:★
解題方向:模擬

希望你有看清楚題目,就不會被範例輸入輸出騙了。


古城之戀


難度:★★★★
解題方向:模型建立、DFS

原題:IOI 2012 - Ideal city

IOI題  互相討論一下吧。

生物實驗


難度:★★
解題方向:DP

兩次LCS取最佳解,處理分數時記得先判斷0的狀況。

寂寞吊燈


難度:★★★
解題方向:D&C、隨機方法。

裸最近點對,支援$O(N^2)$ 剪枝假解。

竹園崗


難度:★★
解題方向:DP

原題:UVA 10534
兩次 LIS 拼在一起取最佳解即可。

由大樹堆起的雜念


難度:★★★★
解題方向:塊狀練表、莫對數組、BIT

原題:TIOJ 1694
離線區間逆序數對。莫對算是比較好寫的方法,複雜度$O(N \sqrt N logN)$,至於$O(N \sqrt N)$支援在線的做法請參考下方資料。

參考資料:CBD

陰謀論


難度:★
解題方向:模擬

判斷數字相等也不會?那你真的沒救了。

如果你還是WA,給你一些怪怪的測資
100 100.00
0.6666666666666666667   0.6666666666666666666
-1 1
-1 -1

2015年8月21日 星期五

[TIOJ 1603] 胖胖殺蚯事件

原題:http://tioj.ck.tp.edu.tw/problems/1603
AC:http://tioj.ck.tp.edu.tw/submissions/17785

解題方向:RMQ


題目大意:區間最大值減最小值

解法:RMQ裸題,沒有修改,所以想要線段樹、Treap、莫對、塊狀練表等等做一做就會AC了,可以拿來實驗很多新方法的題目XD。這裡使用的是Sparse Table,因為沒有修改,顯得特別有優勢。

看到學弟最近在寫這題,無聊來刷了一下Rank~




列舉各方法複雜度
方法 建構資料 查詢 備註
Sparse Table $n\log n$ $1$ 黑魔法加持
線段樹 $n\log n$ $\log n$
樹堆 $n\log n$ $\log n$ 常數大
莫對數組 $n\log n+n\sqrt{n}$ $1$
塊狀鍊表 $n\sqrt{n}$ $\sqrt{n}$

學高級技巧時,也別忘了最單純的想法


#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
ll spM[17][100001];
ll spm[17][100001];
int main()
{
    #ifdef DBG
    //freopen("case.in", "r" , stdin);
    #endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int N,Q,gap,jmp;
    cin>>N>>Q;
    for(int i=1;i<=N;++i)
    {
        cin>>spM[0][i];
        spm[0][i]=spM[0][i];
    }
    for(int i=1; (1<<i)<=N; ++i )
    {
        jmp = ( 1<<(i-1) );
        for(int j=1; j+jmp<=N; ++j )
        {
            spM[i][j] = max( spM[i-1][j] , spM[i-1][j+jmp] ); 
            spm[i][j] = min( spm[i-1][j] , spm[i-1][j+jmp] );
        }
    }
    while(Q--)
    {
        int L,R;
        cin>>L>>R;
        gap = 31 - __builtin_clz(abs(L-R)+1);
        cout<< max( spM[gap][L],spM[gap][R-(1<<gap)+1] )
              -min( spm[gap][L],spm[gap][R-(1<<gap)+1] ) << '\n';
    }
}
同場加映線段樹
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pii;
pii data[400000];
pii merge(const pii &a,const pii &b)
{
    return { min(a.first,b.first) , max(a.second,b.second) };
}

void update(int i,ll v,int L,int R,int I)
{
    if(L==R){
        data[I]={v,v};
        return ;
    }
    int M = (L+R)/2;
    if(i<=M)update(i,v,L,M,I*2+1);
    else update(i,v,M+1,R,I*2+2);
    data[I] = merge(data[I*2+1],data[I*2+2]);
}

pii find(int l,int r,int L,int R,int I)
{
    if( L==l && R==r ) return data[I];
    int M = (L+R)/2;
    if ( r<=M ) return find(l,r,L  ,M,I*2+1);
    if ( M< l ) return find(l,r,M+1,R,I*2+2);
    return merge(   find(l  ,M,L  ,M,I*2+1),
                    find(M+1,r,M+1,R,I*2+2));
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int N,M,a,b;
    ll t;
    cin>>N>>M;
    for(int i=1;i<=N;++i)
    {
        cin>>t;
        update(i,t,1,N,0);
    }
    while(M--)
    {
        cin>>a>>b;
        auto p = find(a,b,1,N,0);
        cout<<p.second-p.first<<'\n';
    }
    return 0;
}

2015年8月19日 星期三

[TIOJ 1396] 線段覆蓋

原題:http://tioj.ck.tp.edu.tw/problems/1396
AC:http://tioj.ck.tp.edu.tw/submissions/17674

解題方向:單調性,貪心法


題目大意:兩個人分別有不同的兩組線段,有標記放置的位置,試問誰能用最少的線段覆蓋指定區間。

解法:我們可以分開計算兩個人所需的線段,把線段排序後,由左向右掃描,檢查最右界是否能到達終點即可。

本方法最費時間的為排序,總複雜度為$O(n\log n)$

透過特判可以把時間壓到$140ms$,不過$visitorIKC$的$61ms$我還沒頭緒,是不是能在優化到線性複雜度呢? (對方表示他只是極致的壓常數


#include<bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    pair<int,int> d[2][100000];
    int N[2];
    int X,Y;
    int ans[2];
    register int i,j,mx,ed;
    bool flag;
    while( cin>>N[0]>>N[1] )
    {
        ans[0]=ans[1]=INT_MAX;
        for(i=0;i<2;++i)
            for(j=0;j<N[i];++j)
                cin>>d[i][j].first>>d[i][j].second;
        cin>>X>>Y;
        flag = false;
        if( X == Y ) {
            for(i=0;i<100000000;++i);
            for(i=0;i<2;++i)
                for(j=0;j<N[i];++j)
                    if( d[i][j].first <= X && X <= d[i][j].second )
                    {
                        ans[i] = 1 ;
                        break;
                    }
        }
        else
        {
            sort(d[0],d[0]+N[0]);
            sort(d[1],d[1]+N[1]);
            for(i=0;i<2;++i)
            {
                ed = mx = X;
                j = 0;
                while( j!=N[i] && ed < Y )
                {
                    ans[i]++;
                    ed = mx;
                    flag = false;
                    while( j!=N[i] && d[i][j].first <= ed ) {
                        if( d[i][j].second > mx )
                        {
                            flag = true;
                            mx = d[i][j].second;
                        }
                        j++;
                    }
                    if( !flag ) break;
                }
                if( mx < Y ) ans[i] = INT_MAX;
            }
        }
        if( ans[0] == ans[1] && ans[0] == INT_MAX ) cout<<"TIE\n";
        else if( ans[0] <= ans[1] ) cout<<"A WIN\n";
        else cout<<"B WIN\n";
    }
}