ZJUT2022寒假集训1 基础专题1 程序简注

A – Sticks

因为POJ出了点问题,于是用了洛谷上的同位题【P1120 小木棍】进行测评。进阶剪枝经典例题,里面涉及到了很多剪枝思想,以下一一注释在程序里面。

namespace lovelive{
    int ai[100],n,sums,nxt[100];
    bool used[100];
    bool dfs(int cur,int tar,int fin,int r){
        //cur : 当前已经拼接的长度
        //tar : 目标长度
        //fin : 已经拼接完成的棍子数量
        //r   : 寻找答案范围的最右端(从小到大排序的)
        if(cur==tar){
            cur = 0; fin++; r = n;  // 注意重新开始拼接的时候
                                    // 也要重置搜寻合适木棍的最右端
            if(tar*fin==sums)return true;
        }
        for(int i=upper_bound(ai+1,ai+r+1,tar-cur)-ai-1;i>0;i--){
            // 【排除等效冗余】既然比ai[r]大的都考虑过了
            //            限制了r也就减少了冗余搜索操作
            if(!used[i]){
                used[i]=true;
                if(dfs(cur+ai[i],tar,fin,i-1))return true;
                used[i]=false;

                if(tar-cur==ai[i]) return false;
                // 【可行性剪枝】选中这根棍子就可以凑成一整根
                // 如果当前的棍子选中了却无法凑出剩下的棍子
                // 这之前的拼法就有问题,用其他的棍子拼完当前也无济于事
                if(cur==0)return false;
                // 【可行性剪枝】与上面的问题差不多,
                // 这跟棍子在完全为空的棍子组合里用不到,
                // 也别指望它能在别的有其他棍子的组合里用到
                i=nxt[i];
                // 【排除等效冗余】相同的木棍结果也是一样的
            }
        }
        return false;
    }
    bool main(){
        cin>>n;if(!n)return false;
        sums = 0;
        for(int i=1;i<=n;i++){
            cin>>ai[i];sums += ai[i];
        }
        sort(ai+1,ai+n+1);          //【顺序剪枝】
        memset(used,0,sizeof(used));
        for(int i=1;i<=n;i++){
            if(ai[i]==ai[i-1])nxt[i] = nxt[i-1];
            else nxt[i] = i;
            //【排除等效冗余】加快寻找下一个方案的速度
        }
        // 【可行性剪枝】但有些不一样,
        // 因为直接接成一整根一定可行,就不用更麻烦的方法考虑。
        for(int parts=sums/ai[n]+1,tar;parts>1;parts--){
            if(sums % parts)continue;
            tar = sums / parts;
            if(dfs(0,tar,0,n)){
                cout<<tar<<endl;;
                return true;
            }
        }
        cout<<sums<<endl;
        return true;
    }
}

B – 矩阵乘法

C – 矩阵快速幂

C – 矩阵快速幂

之前就做过ZJUT2022入门题单Week3 – L – Matrix Power Series了,所以这题看上去就像是只因本功。但矩阵快速幂有意思的在后面,所以告诫自己稍安勿躁,打好基础为重。

namespace lovelive{
    typedef long long VAL;
    typedef vector<vector<VAL>> MAT;
    const int MOD = 1e9+7;
    int matn,m;
    MAT honoka,kotori,umi;
    MAT build(int spec=0){
        MAT res;
        res.resize(matn+1);
        for(int i=1;i<=matn;i++){
            res[i].resize(matn+1);
            res[i][i] = spec;
        }
        return res;
    }
    MAT add(MAT a,MAT b){
        MAT res = a;
        for(int i=1;i<=matn;i++){
            for(int j=1;j<=matn;j++){
                res[i][j] += b[i][j];
            }
        }
        return res;
    }
    MAT mul(MAT a,MAT b){
        MAT res = build();
        for(int i=1;i<=matn;i++){
            for(int j=1;j<=matn;j++){
                for(int k=1;k<=matn;k++){
                    res[i][j] += a[i][k] * b[k][j];
                    res[i][j] %= MOD;
                }
            }
        }
        return res;
    }
    MAT qpow(MAT a,int pown){
        MAT base = a, res = build(1);
        while(pown){
            if(pown&1)res = mul(res,base);
            base = mul(base,base);
            pown >>= 1;
        }
        return res;
    }
    void input(MAT& ai){
        for(int i=1;i<=matn;i++){
            for(int j=1;j<=matn;j++){
                cin>>ai[i][j];
            }
        }
    }
    void output(MAT ai){
        for(int i=1;i<=matn;i++){
            for(int j=1;j<=matn;j++){
                cout<<ai[i][j]<<" ";
            }
            cout<<endl;
        }
    }
    void main(){
        cin>>matn>>m;
        umi = build();
        //kotori = build();
        input(umi);
        //input(kotori);
        //honoka = mul(umi,kotori);
        honoka = qpow(umi,m);
        output(honoka);
    }
}

D – 233 Matrix

矩阵快速幂应用之关键,在于原矩阵到目标矩阵之间关系矩阵,因而找出关系矩阵,并且对该关系矩阵进行快速幂,就有机会在得到答案的同时大幅度提高效率。

namespace lovelive{
    typedef long long VAL;
    typedef vector<vector<VAL>> MAT;
    const int MOD = 10000007;
    int matn,m;
    MAT honoka,kotori,umi;
    /*矩阵运算函数与B、C题同,
    注意行列不统一,但符合计算条件的话,
    可以等效于用不到的单元格全都是0.*/
    bool main(){
        if(!(cin>>matn>>m))return false;
        matn += 2;
        kotori = build();
        kotori[1][1] = 233;
        for(int i=2;i<matn;i++)cin>>kotori[1][i];
        kotori[1][matn] = 3;

        umi = build(1);
        umi[1][1] = 10;
        umi[1][matn] = 0;
        umi[matn][1] = 1;
        for(int i=2;i<matn;i++){
            umi[1][i] = 1;
            for(int j=2;j<i;j++)umi[j][i]=1;
        }
        honoka = mul(kotori,qpow(umi,m));
        cout<<honoka[1][matn-1]<<endl;
        return true;
    }
}

E – Fast Matrix Calculation

几十乘几十的矩阵进行暴力运算,计算机就有点受不了了(高代朱妈语),更别说直接 $A \centerdot B$ 的可能会上千的矩阵了。与其真的如朱妈所言把矩阵的简化运算性质转移到代码中,不如对于计算本身做一些微操。注意到对于矩阵,虽然交换律不成立,但结合律仍旧可用。那么就这样吧!由于$k \geq 6$,六乘六的矩阵总比上千的好太多!

$$
(A \cdot B) \cdot (A \cdot B)^{(N \cdot N – 2)} \cdot (A \cdot B) = A \cdot (B \cdot A)^{(N \cdot N – 1)} \cdot B
$$

因为涉及到了非方阵矩阵的运算,所以把矩阵的相关算法做了一些修改,就不能被省略了。

namespace lovelive{
    typedef long long VAL;

    struct MAT{
        int n,m;
        vector<vector<VAL>> ai;
        MAT(int ni=0,int mi=-1){
            if(mi==-1)mi=ni;
            n = ni;m = mi;
            if(ni==-1)return;
            ai.resize(n+1);
            for(int i=1;i<=n;i++){
                ai[i].resize(m+1);
            }
        }
    }ERR(-1);
    const int MOD = 6;
    int n,m;
    MAT honoka,kotori,umi;
    MAT build(int n,int m,int spec=0){
        MAT res(n,m);
        for(int i=1;i<=min(n,m);i++){
            res.ai[i][i] = spec;
        }
        return res;
    }
    MAT add(MAT a,MAT b){
        MAT res = a;
        for(int i=1;i<=a.n;i++){
            for(int j=1;j<=a.m;j++){
                res.ai[i][j] += b.ai[i][j];
            }
        }
        return res;
    }
    MAT mul(MAT a,MAT b){
        MAT res(a.n,b.m);
        for(int i=1;i<=a.n;i++){
            for(int j=1;j<=b.m;j++){
                for(int k=1;k<=a.m;k++){
                    res.ai[i][j] += a.ai[i][k] * b.ai[k][j];
                    res.ai[i][j] %= MOD;
                }
            }
        }
        return res;
    }
    MAT qpow(MAT a,int pown){
        MAT base = a, res = build(a.n,a.n,1);
        while(pown){
            if(pown&1)res = mul(res,base);
            base = mul(base,base);
            pown >>= 1;
        }
        return res;
    }
    void input(MAT& a){
        for(int i=1;i<=a.n;i++){
            for(int j=1;j<=a.m;j++){
                cin>>a.ai[i][j];
            }
        }
    }
    bool main(){
        cin>>n>>m;
        if(!n||!m)return false;
        kotori = build(n,m);
        input(kotori);
        umi = build(m,n);
        input(umi);
        honoka = mul(umi,kotori);
        honoka = qpow(honoka,n*n-1);
        honoka = mul(kotori,honoka);
        honoka = mul(honoka,umi);
        VAL ans=0;
        for(int i=1;i<=honoka.n;i++){
            for(int j=1;j<=honoka.m;j++){
                ans += honoka.ai[i][j];
            }
        }
        cout<<ans<<endl;
        return true;
    }
}

F – 世界冰球锦标赛

将原规模为$2^{40}$的问题折半为两个$2^{20}$的子问题,最后并在一起考虑。因为一个奇怪的long long卡了半天。

namespace lovelive{
    LL ai[41],bi[1050000],ci[1050000];
    int n;
    long long cntb,cntc,ans,tmp,tmp2,m;
    void main(){
        cin>>n>>m;
        for(int i=0;i<n;i++){
            cin>>ai[i];
        }
        cntb=cntc=0;//一场都不看也是一种方案
        for(int i=0;i<(1<<((n+1)/2));i++){
            tmp=i;tmp2=0;
            for(int j=0;j<(n+1)/2;j++){
                if((1<<j)&i)tmp2 += ai[j];
            }
            if(tmp2<=m){
                bi[++cntb]=tmp2;
            }
        }
        for(int i=0;i<(1<<(n/2));i++){
            tmp=i;tmp2=0;
            for(int j=0;j<n/2;j++){
                if((1<<j)&i)tmp2 += ai[j+(n+1)/2];
            }
            if(tmp2<=m){
                ci[++cntc]=tmp2;
            }
        }
        sort(bi+1,bi+cntb+1);
        sort(ci+1,ci+cntc+1);
        ans = 0;
        for(int i=1;i<=cntb;i++){
            cout<<ci[upper_bound(ci+1,ci+cntc+1,min(m-bi[i],m))-ci-1]<<endl;
            ans += max((int)(upper_bound(ci+1,ci+cntc+1,min(m-bi[i],m))-ci-1),0);
        }
        cout<<ans<<endl;
    }
}

G – 4 Values whose Sum is 0

折半处理问题,用双指针减少寻找答案复杂度(回想起了以前肝力扣周赛天天双指针的痛)。注意可能会存在多种方案效果相同。

namespace lovelive{
    LL ai[4][4005],bi[16000005],ci[16000005];
    int n,bcnt,ccnt,bpos,cpos1,cpos2,ans;
    void main(){
        cin>>n;bcnt=0;ccnt=0;
        for(int i=1;i<=n;i++){
            for(int j=0;j<4;j++){
                cin>>ai[j][i];
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                bi[++bcnt]=ai[0][i]+ai[1][j];
            }
        }
        sort(bi+1,bi+bcnt+1);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                ci[++ccnt]=ai[2][i]+ai[3][j];
            }
        }
        sort(ci+1,ci+ccnt+1);
        cpos1=cpos2=ccnt;ans=0;
        for(bpos=1;bpos<=bcnt;bpos++){
            while(cpos1&&bi[bpos]+ci[cpos1]>0)cpos1--;
            while(cpos2&&bi[bpos]+ci[cpos2]>=0)cpos2--;
            ans += cpos1-cpos2;
        }
        cout<<ans<<endl;
    }
}

H – Coin Change (IV)

跟上面几道题类似的思路,不同的是这里用到了三进制(

//输出在lovelive空间外面进行,该函数不会在标准输出输出任何东西
namespace lovelive{
    LL ai[20],bi[60000],ci[60000],m,tmp2;
    int n,bcnt,ccnt,bpos,cpos1,cpos2,ans,mid;
    int main(){
        cin>>n>>m;bcnt=0;ccnt=0;
        for(int i=1;i<=n;i++){
            cin>>ai[i];
        }
        mid = (n+1)/2;
        //出现了,是三进制!
        for(int i=0,tar=pow(3,mid),j,tmp;i<tar;i++){
            j = 1;tmp = i;tmp2 = 0;
            while(tmp){
                tmp2 += ai[j] * (tmp % 3);
                tmp /= 3; j++;
            }
            bi[++bcnt] = tmp2;
        }
        sort(bi+1,bi+bcnt+1);
        for(int i=0,tar=pow(3,n/2),j,tmp;i<tar;i++){
            j = mid + 1;tmp = i;tmp2 = 0;
            while(tmp){
                tmp2 += ai[j] * (tmp % 3);
                tmp /= 3; j++;
            }
            ci[++ccnt] = tmp2;
        }
        sort(ci+1,ci+ccnt+1);
        cpos1=cpos2=ccnt;ans=0;
        for(bpos=1;bpos<=bcnt;bpos++){
            while(cpos1&&bi[bpos]+ci[cpos1]>m)cpos1--;
            while(cpos2&&bi[bpos]+ci[cpos2]>=m)cpos2--;
            ans += cpos1-cpos2;
        }
        return ans;
    }
}

I – Subsequence

由于数据集全是正数,可以保证前缀和严格单调,此时可以上双指针。

namespace lovelive{
    int n,l,r,ans;
    LL m,ai[100005],sumi[100005];
    void main(){
        cin>>n>>m;sumi[0]=0;ans=n;
        for(int i=1;i<=n;i++){
            cin>>ai[i];
            sumi[i] = sumi[i-1]+ai[i];
        }
        if(sumi[n]<m){
            cout<<0<<endl;
            return;
        }
        for(l=r=1;l<=n;l++){
            while(r<=n&&sumi[r]-sumi[l-1]<m)r++;
            if(r>n)break;
            ans = min(ans,r-l+1);
        }
        cout<<ans<<endl;
    }
}

J – Graveyard Design

注意到又是连续序列并且前缀和能保持严格单调。双指针!

namespace lovelive{
    long long n,l,r,sumi,tar;
    vector<pair<int,int>> ansi;
    void main(){
        cin>>n;sumi = 0;
        tar = sqrt(n);
        for(l=r=1;l<=tar;l++){
            while(r<=tar&&sumi<n){
                sumi += r*r;
                r++;
            }
            if(sumi==n)ansi.push_back(make_pair(l,r));
            sumi-=l*l;
        }
        cout<<ansi.size()<<endl;
        for(int i=0;i<ansi.size();i++){
            cout<<ansi[i].second-ansi[i].first;
            for(int j=ansi[i].first;j<ansi[i].second;j++){
                cout<<" "<<j;
            }
            cout<<endl;
        }
    }
}

K – 斐波那契数列

如果能把D题的关系矩阵想清楚的话,斐波那契的关系矩阵就变得十分easy了。代码用的是E题的模板。

namespace lovelive{
    const int MOD = 1e9+7;
    long long ni;
    void main(){
        cin>>ni;
        if(ni==1){
            cout<<1<<endl;return;
        }
        ni-=2;
        kotori = build(2);
        kotori.ai[1][1] = kotori.ai[1][2] = 1;
        //output(kotori);
        umi = build(2);
        umi.ai[1][1] = umi.ai[1][2] = umi.ai[2][1] = 1;
        //output(qpow(umi,ni));
        honoka = mul(kotori,qpow(umi,ni));
        cout<<honoka.ai[1][1]<<endl;
        return ;
    }
}
知识共享许可协议
《ZJUT2022寒假集训1 基础专题1 程序简注》在文章中没有特殊说明的情况下采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。请在转载时注明作者(从现瑕疵)和来源(https://www.zhtg.net.cn/zjut2022_win01/)
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇