• 3.3-3.9 周记


    3.3-3.10

    1. NIM游戏

    百度链接:https://baike.baidu.com/item/Nim游戏/6737105?fr=aladdin

    定义:

    • P局面:先手必败
    • N局面:先手必胜

    P局面的所有子局面都是N局面。N局面的子局面中必有一个是P局面

    性质:(a_1 xor a_2 xor cdots xor a_n = 0) 则为P局面

    证明:

    若某个局面异或结果大于0,则一定存在一个操作使得(a_1 xor a_2 xor cdots xor a_n = 0)

    若某个局面异或结果等于0,则一定不存在一个操作使得(a_1 xor a_2 xor cdots xor a_n = 0)

    因为若Nim和为X,X得二进制表示最左边的1在第k位,则一定存在一个该位为1的堆。设这堆火柴数量为Y,则只需把它拿成 Z = Y xor X 根火柴,得到的状态就是必败状态。

    2. chomp!游戏

    m*n的棋盘,每次取走一个方格并拿掉它右边和上面的所有方格。拿到(1,1)的人输。

    先手必胜,因为先手可以在后手先一步达到必胜状态。

    3. SG函数

    SG(x) = mex(S)

    4. 动归复习

    1. LCIS

    求两个序列的最长公共上升子序列

    d[i][j]表示A的前i个与B的前j个,并且以(B_j)为结尾的LCIS长度。为啥一定要带这个结尾信息呢?因为进行下一次拼接时,必须知道结尾信息,否则不能满足上升关系。

    • A[i] != B[j]时,d[i][j] = d[i-1][j]
    • A[i] == B[j]时,(d[i][j] = max_{{1le k<j, B_k<A_i}} {d[i-1][k]}+1)
    #include <bits/stdc++.h>
    using namespace std;
    int n;
    int A[3030],B[3030];
    int d[3030][3030];
    int main(){
        cin>>n;
        for(int i=1;i<=n;i++)scanf("%d",&A[i]);
        for(int i=1;i<=n;i++)scanf("%d",&B[i]);
        int ans = 0;
        for(int i=1;i<=n;i++){
            int val = 0;
            for(int j=1;j<=n;j++){
                if(B[j] == A[i]){
                    d[i][j] = val+1;
                }
                else d[i][j] = d[i-1][j];
                if(B[j]<A[i]) val = max(val,d[i-1][j]);
                ans = max(ans,d[i][j]);
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

    2. POJ-3666

    $S = |A_1 - B_1| + |A_2 - B_2| + ... + |A_N - B_N | $

    给出A数组,求一个不下降或者不上升的B数组,使得S值最小

    可以猜到的是,B数组中的所有元素都在A中出现过。(数学归纳法可以证明,但也要靠直觉)

    然后既然A来自B,那么也就是说可以先把A复制一份,然后从中选某些数字,进而得到B。

    (d[i][j]) 表示A数组的前 i 个,当以(B_j)结尾时,S的最小值

    (d[i][j] = min_{1le kle j} d[i-1][k] + abs(A[i]-B[j]))

    其中B必须先离散化,随着j有序之后才可以。

    ll a[2010],b[2010],d[2010];
    int n;
    ll abs(ll a){
        return a>0?a:-a;
    }
    ll dp()
    {
    	for(int i=1;i<=n;i++)
    		d[i] = abs(a[1]-b[i]);
    	for(int i=2;i<=n;i++)
    	{
    		ll mi = d[1];
    		for(int j=1;j<=n;j++)
    		{
    			mi = min(mi,d[j]);
    			d[j] = mi + abs(a[i]-b[j]);
    		}
    	}
    	ll ans = d[1];
    	for(int i=2;i<=n;i++)
    		ans = min(ans,d[i]);
    	return ans;
    }
    bool cmp(ll a,ll b)
    {
    	return a>b;
    }
    int main() 
    {
    	cin>>n;
        for(int i=1;i<=n;i++)
        {
        	scanf("%lld",&a[i]);
        	b[i] = a[i];
        }
        sort(b+1,b+1+n);
        ll ans = dp();
        sort(b+1,b+1+n,cmp);
        ans = min(ans,dp());
        cout<<ans<<endl;
        return 0;
    }
    

    3. CH-5102 Mobile Service

    三个员工在1,2,3。

    要求按照p序列,必须有一个员工到达对应位置。求最少距离。要求一个地方不能有两个员工。

    很容易想到$d[i][x][y][z] $ 表示当前为p[i],三个员工为x,y,z位置时的最少花费。

    但是该算法规模为 (1000*200^3) 故不能承受。

    但是考虑到 i 时刻,必然有一个员工在p[i]位置。所以可以节约一维。另外转移的时候一定要注意,不能有两个人在同一个地方

    #include <bits/stdc++.h>
    using namespace std;
    const int inf = 0x3f3f3f3f;
    int cost[220][220];
    int L,n,p[1010];
    int d[1010][220][220];
    int main(){
        cin>>L>>n;
        for(int i=1;i<=L;i++)
            for(int j=1;j<=L;j++)scanf("%d",&cost[i][j]);
        for(int i=1;i<=n;i++)scanf("%d",&p[i]);
        p[0] = 1;
        memset(d,0x3f,sizeof d);
        d[0][2][3] = 0;
        for(int i=0;i<n;i++){
            for(int j=1;j<=L;j++){
                for(int k=1;k<=L;k++){
                    if(p[i+1]!=j&&p[i+1]!=k)
                    d[i+1][j][k] = min(d[i+1][j][k],d[i][j][k] + cost[p[i]][p[i+1]]);
                    if(p[i+1]!=k&&p[i+1]!=p[i])
                    d[i+1][p[i]][k] = min(d[i+1][p[i]][k],d[i][j][k] + cost[j][p[i+1]]);
                    if(p[i+1]!=j&&p[i+1]!=p[i])
                    d[i+1][j][p[i]] = min(d[i+1][j][p[i]],d[i][j][k] + cost[k][p[i+1]]);
                }
            }
        }
        int res = inf;
        for(int i=1;i<=L;i++)
            for(int j=1;j<=L;j++){
                if(i!=p[n]&&j!=p[n])
                res = min(res,d[n][i][j]);
            }
        cout<<res<<endl;
        return 0;
    }
    

    4. CH-5103 传纸条

    d[i][x1][x2] 表示走了 i 步,一条路径在x1,一条路径在x2时的最大权值和

    #include <bits/stdc++.h>
    using namespace std;
    int n,m;
    int a[55][55];
    int d[110][55][55];
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)scanf("%d",&a[i][j]);
        d[0][1][1] = a[1][1];
        for(int i=0;i<n+m-2;i++){
            for(int x1 = 1;x1<=1+i&&x1<=n;x1++){
                for(int x2 = x1;x2<=1+i&&x2<=n;x2++){
                    int y1 = i+2-x1;
                    int y2 = i+2-x2;
                    if(y1>m||y2>m)continue;
                    d[i+1][x1][x2+1] = max(d[i+1][x1][x2+1],d[i][x1][x2]+a[x1][y1+1]+a[x2+1][y2]);
                    if(x1==x2){
                        d[i+1][x1][x2] = max(d[i+1][x1][x2],d[i][x1][x2]+a[x1][y1+1]);
                        d[i+1][x1+1][x2+1] = max(d[i+1][x1+1][x2+1],d[i][x1][x2]+a[x1+1][y1]);
                    }
                    else{
                        d[i+1][x1][x2] = max(d[i+1][x1][x2],d[i][x1][x2] + a[x1][y1+1]+a[x2][y2+1]);
                        d[i+1][x1+1][x2+1] = max(d[i+1][x1+1][x2+1],d[i][x1][x2]+a[x1+1][y1]+a[x2+1][y2]);
                        if(x1==x2-1){
                            d[i+1][x1+1][x2] = max(d[i+1][x1+1][x2],d[i][x1][x2]+a[x1+1][y1]);
                        }
                        else d[i+1][x1+1][x2] = max(d[i+1][x1+1][x2],d[i][x1][x2]+a[x1+1][y1]+a[x2][y2+1]);
                    }
                }
            }
        }
        cout<<d[n+m-2][n][n]<<endl;
        return 0;
    }
    

    5. CF-1118

    D2 - Coffee and Coursework (Hard Version)

    • 很容易想到的二分,在一个处理上面卡壳了
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 200010;
    typedef long long ll;
    int n,m;
    ll a[N],b[N];
    bool cmp(ll a,ll b){return a>b;}
    bool check(int mid){
        ll sum = 0;
        for(int i=0;;i++){
            if(mid+i*mid<=n&&a[mid+i*mid]>=i){
                sum += b[(i+1)*mid] - b[i*mid] - i*(mid);
            }
            else{
                int index;
                for(index = i*mid+1;index<=n&&index<=(i+1)*mid;index++)
                    if(a[index]<=i)break;
                sum += b[index-1]-b[i*mid]-i*(index - i*mid -1);
                break;
            }
        }
        return sum>=m;
    }
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++){
            b[i] = b[i-1]+a[i];
        }
        int l = 1,r = n;
        while(l<r){
            int mid = (l+r)/2;
            if(check(mid))r = mid;
            else l = mid+1;
        }
        if(check(r))
            cout<<r<<endl;
        else cout<<-1<<endl;
        return 0;
    }
    

    6. CF-1121

    C - System Testing

    • 烦人的模拟,一开始理解错题意了,看题这种事情实在是不能马虎
    #include <bits/stdc++.h>
    using namespace std;
    const int inf = 0x3f3f3f3f;
    int n,m;
    int now[110],a[1010],pre[110],now_id[110],has[1010];
    int round(int x,int y){
        return (int)((double)x*100/y+0.5);
    }
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        priority_queue< pair<int,int> > q;
        int num = 0;
        for(int i=1;i<=m;i++){
            now[i] = a[i];
            pre[i] = 1;
            now_id[i] = i;
            q.push(make_pair(-a[i],i));
        }
        int now_time = 0;
        int res = 0,t = m;
        for(int now_time = 1;now_time<=150*1000;now_time++){
            for(int i=1;i<=m;i++){
                if(now[i]<now_time){
                    num++;
                    if(t<n){
                        now[i] += a[++t];
                        pre[i] = now_time;
                        now_id[i] = t;
                    }
                    else{
                        now[i] = inf;
                        pre[i] = inf;
                    }
                }
            }
            int rou = round(num,n);
            for(int i=1;i<=m;i++){
                if(now_time-pre[i]+1==rou){
                    has[now_id[i]] = 1;
                }
            }
            if(num == n)break;
        }
        for(int i=1;i<=n;i++)if(has[i])res++;
        cout<<res<<endl;
        return 0;
    }
    

    7. CF-1036

    C - Classy Numbers

    • 组合数
    • 寻思了挺久,不过1A。大概还是用到了部分递推。函数写了一堆
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    ll L,R;
    int T;
    ll C[20][20];
    ll A[20][20];
    ll po[20];
    //初始化组合数和10的n次方
    void init(){
        C[0][0] = 1;
        for(int i=1;i<=18;i++){
            C[i][0] = 1;
            for(int j=1;j<=i;j++){
                C[i][j] = C[i-1][j-1] + C[i-1][j];
            }
        }
        po[0] = 1;
        for(int i=1;i<=18;i++)po[i] = po[i-1]*10;
    }
    //计算某个数长度
    int calc_len(ll x){
        int len = 0;
        while(x){
            x/=10;len++;
        }
        return len;
    }
    //计算某个数的头位有多大
    int calc_left(ll x){
        while(x>=10)x/=10;
        return x;
    }
    ll calc(ll x,ll num){
        if(num==0)return 1;
        ll len = calc_len(x);
        ll max_left = calc_left(x);
        //printf("%lld %lld %lld %lld
    ",x,len,max_left,num);
        ll res = 0;
        for(int i=num-1;i>=0;i--){
            res += (max_left-1)*C[len-1][i]*A[9][i];
        }
        for(int i=num;i>=0;i--)
            res += C[len-1][i]*A[9][i];
        //printf("%lld %lld
    ",x,res);
        res += calc(x-max_left*po[len-1],num-1);
        return res;
    }
    int main(){
        init();
        A[9][0] = 1;A[9][1] = 9;A[9][2] = 81;A[9][3] = 729;
        cin>>T;
        while(T--){
            cin>>L>>R;
            //printf("calc(R,3):%lld,calc(L,3):%lld
    ",calc(R,3),calc(L,3));
            ll res = calc(R,3)-calc(L-1,3);
            printf("%lld
    ",res);
        }
        return 0;
    }
    
    • 方法二:先把所有满足要求的数字求出来,然后二分划定区间。可以求出这样的数字不是特别多 $9*10^2 * A_{18}^3 = 4406400 $
    #include <bits/stdc++.h>
    
    #define forn(i, n) for (int i = 0; i < int(n); i++)
    
    using namespace std;
    
    vector<long long> res;
    //可以说非常巧妙了
    void brute(int pos, int cnt, long long cur){
        if (pos == 18){
            res.push_back(cur);
            return;
        }
        
        brute(pos + 1, cnt, cur * 10);
        
        if (cnt < 3)
            for (int i = 1; i <= 9; ++i)
                brute(pos + 1, cnt + 1, cur * 10 + i);
    }
    
    int main() {
        brute(0, 0, 0);
        res.push_back(1000000000000000000);
        
        int T;
        scanf("%d", &T);
        forn(i, T){
            long long L, R;
            scanf("%lld%lld", &L, &R);
            printf("%d
    ", int(upper_bound(res.begin(), res.end(), R) - lower_bound(res.begin(), res.end(), L)));
        }
        return 0;
    }
    

    8. CF-1132

    C - Painting the Fence

    • 第一眼没看出来是个可以暴力的的题目
    #include <bits/stdc++.h>
    using namespace std;
    int n,q;
    int a[5010],b[5050],c[5050];
    struct node{
        int l,r;
    }k[5050];
    bool cmp(node x,node y){
        if(b[x.r]-b[x.l-1]==b[y.r]-b[y.l-1]){
            return x.r-x.l<y.r-y.l;
        }
        return b[x.r]-b[x.l-1]<b[y.r]-b[y.l-1];
    }
    int main(){
        cin>>n>>q;
        for(int i=1;i<=q;i++){
            scanf("%d%d",&k[i].l,&k[i].r);
            a[k[i].l]++;
            a[k[i].r+1]--;
        }
        int res = 0;
        for(int i=1;i<=n;i++){
            a[i] += a[i-1];
            if(a[i]!=0)res++;
            b[i] += b[i-1] + (a[i]==1?1:0);
            c[i] += c[i-1] + (a[i]==2?1:0);
            //printf("%d ",c[i]);
        }
        int mi = 0x3f3f3f3f;
        for(int i=1;i<=q;i++){
            
            for(int j=i+1;j<=q;j++){
                int cnt = 0;
                int r = min(k[i].r,k[j].r);
                int l = max(k[i].l,k[j].l);
                cnt += b[k[i].r]-b[k[i].l-1] + b[k[j].r]-b[k[j].l-1];
                if(r>=l) cnt += c[r]-c[l-1];
                mi = min(mi,cnt);
            }
        }
        cout<<res-mi<<endl;
        return 0;
    }
    
    • CF给的教程是枚举每个 i ,然后统计出除 i 之外最优解。然后整体取最优解。

    F - Clear the String

    • 区间dp
    #include <bits/stdc++.h>
    using namespace std;
    int n;
    char s[550];
    int d[550][550];
    int main(){
        cin>>n;
        scanf("%s",s+1);
        memset(d,0x3f,sizeof d);
        for(int i=1;i<=n;i++)d[i][i] = 1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<i;j++){
                for(int k=j;k<i;k++){
                    if(k==i-1){
                        if(s[k]==s[i])d[j][i] = min(d[j][i],d[j][k]);
                        else d[j][i] = min(d[j][i],d[j][k]+1);
                        continue;
                    }
                    if(s[k] == s[i]){
                        d[j][i] = min(d[j][i],d[j][k]+d[k+1][i-1]);
                    }
                    else d[j][i] = min(d[j][i],d[j][k]+d[k+1][i-1]+1);
                }
                //printf("%d %d %d
    ",j,i,d[j][i]);
            }
        }
        cout<<d[1][n]<<endl;
        return 0;
    }
    
  • 相关阅读:
    石头的用途
    [转] Analysis: Khronos and OpenGL ARB merge
    ★○值得你我珍藏一世的80句话○★
    PasswordStrength 控件
    NumericUpDownExtender 控件
    ReorderList控件
    Nobot控件拒绝机器人行为
    PopupControlExtender控件
    PagingBulletedList 控件学习
    MutuallyExlcusiveCheckBox控件
  • 原文地址:https://www.cnblogs.com/1625--H/p/10499651.html
Copyright © 2020-2023  润新知