头像

朱星星




离线:4小时前


最近来访(66)
用户头像
诗和远方
用户头像
dancin
用户头像
_Ex1le
用户头像
张_691
用户头像
郑佰亿.
用户头像
LRone
用户头像
Tobt
用户头像
hzh..
用户头像
clwysama
用户头像
梦小洛
用户头像
黎笑涵
用户头像
爱莉希雅
用户头像
不高兴的兽奶
用户头像
acss
用户头像
苏钰湫
用户头像
凌乱之风
用户头像
张小宝的哥哥
用户头像
垫抽底风
用户头像
嗄都
用户头像
涵虚混太清


题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$
  • 这个题的话是得找到有多少对(l, r) 满足 R >= sum[l~r] >= L
    一般遇到这样的题我们都要分解sum[l~r],然后再继续推式子
    所以就 R >= sum[l-r] >= L ->> R >= sum[r] - sum[l-1] >= L
    sum[r] - L >= sum[l-1] >= sum[r] - R;

  • 所以对于一个给定的右端点, 那么我们只需要查询在这之前有多少个前缀和在
    [sum[r]-R,sum[l]-L] 之内, 然后累加就行了

  • 而 sum[r] - R比较大 我们动态开点就行了。

时间复杂度

参考文献

C++ 代码

#include<iostream>

using namespace std;

const int N = 1e5 + 10;
typedef long long LL;
struct node
{
    int ls,rs;
    int sum ;
    node()
    {
        ls = rs = sum = 0;
    }
}tr[N*130];

int idx = 1;

void pushup(int u)
{
    tr[u].sum = tr[tr[u].ls].sum + tr[tr[u].rs].sum;
}

void modify(int &u, LL pos, LL L, LL R,int v)
{
    if(L > pos || pos > R) return;
    if(!u) u = ++idx ;

    if(L == R) 
    {
        tr[u].sum += 1;
        return ;
    }

    LL mid =  (L + R)>>1;

    modify(tr[u].ls , pos, L , mid, v);
    modify(tr[u].rs, pos , mid + 1, R, v);
    pushup(u);
}

int query(int &u,  LL l ,LL r, LL L, LL R)
{
    if(L > r || R  < l || !u) return 0;
    if(L >= l && R <= r) return tr[u].sum;


    LL mid = (L + R) >> 1;
    LL sum = 0;
    sum +=query(tr[u].ls , l, r, L, mid);
    sum +=query(tr[u].rs, l, r, mid + 1, R);
    return sum;
}
LL sum[N];
int main()
{
    int n, L, R, root = 1;
    LL l = -1e10, r = 1e10;
    cin>>n>>L>>R;
    LL res = 0;
    modify(root, 0, l, r, 1);
    for(int i = 1; i <= n; i++)
    {
        int x;
        cin>>x;
        sum[i] = sum[i-1] + x;


        res += query(root, sum[i]-R, sum[i]-L, l, r);
        modify(root, sum[i], l, r, 1);

    }


    cout<<res;

    return 0;
}

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

blablabla



题目描述

在权值线段树当中,若我们的值在(1, 1e9)范围之间,那么建立一颗完整的树肯定是不可能的
所以动态开点线段树就是为此而生。

每一次询问一般只会访问一颗树,那么m次询问,也就是访问m课树,总共用的空间也就是m*logn
大概和时间复杂度一样的。

怎么做呢?

首先我们只确定一个根节点,结构体需要存下它的左儿子与右儿子节点编号,因为这时u<<1, u<<1|1
可能就不是u的儿子了。那么在遍历的时候存一下这个时候的区间L,R当中这个节点的区间范围。

然后当我们的儿子没有被开出来时,我们就给它一个新节点编号,注意我们在函数中要使用引用 &u
那么改变之后,父节点的信息也会得到改变。

struct node
{
    int ls, rs;
    LL sum, cnt;
    node()
    {
        ls = rs = cnt = sum = 0;
    }
}tr[N*40];
void modify(int &u, int l, int r, LL v, int L, int R)
{
    if(L > r || R < l) return ;
    if(!u) u = ++idx;
    if(L >= l && R <= r) 
    {
        tr[u].sum += r  * v;
        tr[u].cnt += v;
        return ;
    }

    int mid = (L + R) >> 1;
    modify(tr[u].ls, l, r, v, L, mid);
    modify(tr[u].rs, l, r, v, mid + 1, R);
    pushup(u);
}

总的来说,就是破坏 u << 1, u << 1 | 1, 这种结构,用节点编号来代替。

样例






算法1

(暴力枚举) $O(n^2)$
  • 这个题最重要的是如何实现第三个操作,查询最大x个点的价值的和,所以我们肯定要从最大的价值点开始,把它作为线段树维护的值的话,我们发现时间复杂度根本不行。
    所以我们考虑把它作为下标,那么我们就从从大到小二分区间,用动态开点树实现就行了。

  • 在结构体中分别维护 b[i] 和a[i]*b[i], 第一个操作和第二个操作单点修改就行了

时间复杂度

参考文献

C++ 代码

blablabla

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

#include<iostream>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
int a[N], b[N];
struct node
{
    int ls, rs;
    LL sum, cnt;
    node()
    {
        ls = rs = cnt = sum = 0;
    }
}tr[N*40];
int idx = 1;

void pushup(int u)
{
    tr[u].sum = tr[tr[u].ls].sum + tr[tr[u].rs].sum; 
    tr[u].cnt = tr[tr[u].ls].cnt + tr[tr[u].rs].cnt;
}

void modify(int &u, int l, int r, LL v, int L, int R)
{
    if(L > r || R < l) return ;
    if(!u) u = ++idx;
    if(L >= l && R <= r) 
    {
        tr[u].sum += r  * v;
        tr[u].cnt += v;
        return ;
    }

    int mid = (L + R) >> 1;
    modify(tr[u].ls, l, r, v, L, mid);
    modify(tr[u].rs, l, r, v, mid + 1, R);
    pushup(u);
}

LL query(int &u, int l, int r, int L, int R)
{
    if(L > r || R < l || !u) return 0;
    if(L >= l && R <= r) return tr[u].sum;

    int mid = (L + R) >> 1;
    LL sum = 0;
    sum += query(tr[u].ls , l, r , L , mid);
    sum += query(tr[u].rs, l, r, mid + 1, R);
    return sum;
}

LL query1(int &u, int l, int r, int L, int R)
{
    if(L > r || R < l || !u) return 0;
    if(L >= l && R <= r) return tr[u].cnt;


    int mid = (L + R) >> 1;
    LL sum = 0;
    sum += query1(tr[u].ls, l, r, L , mid);
    sum += query1(tr[u].rs, l, r, mid + 1, R);
    return sum;
}

int main()
{
    int n, m, root = 1, len = 1e9;
    cin>>n;

    for(int i = 1; i <= n; i++) 
    {
        cin>>a[i]>>b[i];
        modify(root, a[i], a[i], b[i], 0, len);
    }


    cin>>m;
    while(m--)
    {
        int op, x, y;
        scanf("%d%d", &op, &x);
        if(op == 1)
        {
            scanf("%d",&y);
            modify(root, a[x], a[x], -b[x], 0, len);
            modify(root, y, y, b[x],  0, len);
            a[x] = y;
        }
        else if(op == 2)
        {
            scanf("%d",&y);
            modify(root, a[x], a[x], -b[x]+y, 0, len);
            b[x] = y;
        }
        else
        {
            if(tr[1].cnt < x) puts("-1");
            else 
            {
                int l = 0, r  = len;
                while(l < r)
                {
                    int mid = l + r + 1 >> 1;

                    if(query1(root, mid, len, 0, len) >= x ) l = mid;
                    else r = mid - 1;
                }

                long long more =(  query1(root, l, len, 0, len) - x )  * l;
                printf("%lld\n",query(root, l, len, 0 ,len) - more);
            }
        }
    }



    return 0;
}



题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$
  • 冒泡排序的特点

  • 每一轮冒泡排序,都会减小整个数列的逆序对,那么根据此我们用差分思想维护每一轮减少的逆序对

  • 我们发现:一个数ai只会被前面大于ai的数aj更新掉,也就是减少ai,的逆序对数。
    显然如果此时一个数ai, 前面没有aj这样的数,那么肯定它的逆序对不会被减少。

  • 假设ai 前面有bi 个aj 这样的数, 那么这些数将会在第 (bi+1) 轮全部冒泡到ai 后面,
    第 (bi + 1)及以后轮, ai就会往后冒泡去减少后面的数的逆序对, 并且ai的逆序对不可能被减少了。

所以在每一轮中,我们只需要知道当前有多少个 前面没有比它大的数 的数的数量, 设cnt个。
然后这些数字必然会减少那些剩余的数字,那么每一轮减少的逆序对为n - cnt

  • 考虑交换带来的影响

  • 若 a[c] > a[c+1], 那么此时交换之后a[c+1]的逆序对数量将会减少1,a[c]不变
    那么原来在第(1, b[c+1])不会对逆序对数量减少造成影响,现在改为(1, b[c+1]-1)轮不会造成影响

那么我们就需要修改差分数组了:原本第 b[c+1]-1 轮 需要减少的为 n - cnt, 那么此时为n-cnt-1

  • 若 a[c] < a[c+1] 类似分析

时间复杂度

参考文献

C++ 代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
int a[N];
typedef long long LL;
struct node
{
    int l, r;
    LL sum;
}tr[N*4];

// tr[u][0] 求逆序对, tr[u][1] 维护每个数逆序对数量

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r)
{
    tr[u]=  {l, r, 0};
    if(l == r) return ;
    int mid = (l + r) >> 1;

    build(u << 1 ,l, mid);
    build(u << 1 | 1, mid + 1, r);
}

void modify(int u, int l, int r , LL v  )
{
    if(tr[u].l > r || tr[u].r < l) return;
    if(tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum += v;
        return ;
    }

    modify(u << 1 ,l, r, v);
    modify(u << 1 | 1, l, r, v);
    pushup(u);
}

LL query(int u, int l, int r)
{
    if(tr[u].l > r || tr[u].r < l) return 0;
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    LL sum = 0;
    sum += query(u << 1, l, r);
    sum += query(u << 1 | 1 , l, r);
    return sum;
}
int b[N], cnt[N];
int main()
{
    int n,m;
    cin>>n>>m;

    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);
    LL sum = 0;
    for(int i = 1; i <= n; i++)
    {
        modify(1, a[i], a[i], 1);
        b[i]  = query(1, a[i] + 1, n);
        cnt[b[i]] ++;
        sum += b[i];
    }

    build(1, 1, n);
    modify(1, 1, 1, sum);
    sum  = 0;
   for(int i = 2; i <= n ; i++)
   {
       sum += cnt[i - 2]; // 前面大于自身的人为 i - 2个的数字的 数量
       modify(1, i, i, - n + sum); // sum 个不需要被减少的个数
   }

     // b[i] 表示前面有多少个大于他的人, 那么在第(1, b[i])轮不会对答案造成影响

    while(m --)
    {
        int t, c;
        cin>>t>>c;
        if(t == 1)
        {
            if(a[c] > a[c + 1]) 
            {
                // 我当前需要修改的是b[c+1] 改为b[c+1] - 1, 也就是说在 (1, b[c+1]-1)轮不会对
                //答案造成影响, 在b[c+1]轮造成影响了,提前一位那么在第b[c+1]-1轮造成影响
                modify(1, b[c + 1]+1, b[c + 1]+1, 1);
                modify(1, 1, 1, -1);
                b[c+1]--;
                swap(a[c], a[c+1]);swap(b[c+1], b[c]);

            }
            else
            {
                // (1, a[c]) -> (1, a[c] + 1) 那么在第a[c]轮不应该造成影响
                modify(1, b[c]+2 , b[c]+2, -1);
                modify(1, 1 ,1 ,1);
                b[c]++;
                swap(a[c], a[c+1]), swap(b[c+1], b[c]);
            }
        }
        else
        {
            if(c>=n)puts("0");
            else printf("%lld\n",query(1, 1, c+1));
        }
    }


    return 0;
}








算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

blablabla



题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$
  • 这样的操作 除法 开根号,取余这样的操作一般都不会超过64次;
    并且这样的操作无法进行区间合并,我们可以也只能暴力修改。

所以我们为每一个区间维护一个tag : 表示这个区间是否还需要进行操作

并且我们也不需要用pushdown ,因为不需要维护懒标记

时间复杂度

参考文献

C++ 代码

#include<iostream>

using namespace std;

const int N = 1e5 + 10;
#define x first
#define y second
typedef long long LL;
typedef pair<LL,LL>PII;
int a[N];
struct node
{
    int l, r;
    LL sum;
    LL d;
    int tag;
}tr[N*4];

inline void pushup(int &u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    tr[u]. tag = tr[u << 1].tag | tr[u << 1 | 1].tag;
    tr[u].d = tr[u << 1].d + tr[u << 1 | 1].d;
}

inline void pushdown(int &u)
{
    LL t = tr[u].d;
    tr[u << 1].sum += t * (tr[u << 1].r - tr[u << 1].l + 1);


    tr[u << 1 | 1].sum += t * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
    tr[u << 1 | 1].d += t;
    tr[u << 1].d += t;
    tr[u].d = 0;
}

void bulid(int l, int r, int u)
{
    if(l == r) tr[u] = {l, r, a[l], a[l]<100?1:0, a[l]<10?0:1};
    else
    {
        tr[u].l = l, tr[u].r = r, tr[u].d = 0;
        int mid = l + r >> 1;
        bulid(l, mid, u << 1);
        bulid(mid + 1, r, u << 1 | 1);
        pushup(u);  
    }
}

void modify(int u, int l, int r)
{
    if(tr[u].r < l || tr[u].l > r || tr[u].tag == 0 ) return;

    if(tr[u].l == tr[u].r)
    {
        if(tr[u].sum%3 == 0) tr[u].sum -= tr[u].sum/3;
        else tr[u].sum -= tr[u].sum/3 + 1;
        if(tr[u].sum  < 10) tr[u].tag = 0;
        if(tr[u].sum < 100) tr[u].d = 1;
        return ;
    }


// 由于上次可能增加了u的懒标记,而此次又需要更新sum, 但儿子节点sum还要依赖tr[u].d
// 和本次的v, 所以我们要把懒标记下下传递,不然pushup的值会错误!!            

    modify(u << 1, l, r);
    modify(u << 1 | 1, l, r);
    pushup(u);
}

void add(PII &a, PII &b)
{
    a.x = a.x + b.x;
    a.y = a.y + b.y;
}

PII query(int u, int l, int r)
{
    if(tr[u].r < l || tr[u].l > r) return {0,0};
    if(tr[u].l >= l && tr[u]. r <= r) return {tr[u].sum, tr[u].d};

    PII tmp = {0,0};
    PII a = query(u << 1, l, r), b = query(u << 1 | 1, l, r);
    add(tmp, a);
    add(tmp,b);

    return tmp;
}

// 2 4 5


int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1; i <= n; i++) cin>>a[i];
    bulid(1, n, 1);

    while(m--)
    {
       int op, l ,r;
       cin>>op>>l>>r;
       if(op ==  3) cout<<query(1, l, r).x<<'\n';
       else if(op == 2)cout<<query(1, l, r).y<<'\n';
       else modify(1, l, r);
    }

    return 0;
}

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

blablabla



题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$
  • 从DP的角度来看


    容易看出这就是一个最长上升子序列模型,f[i] 表示以a[i]为结尾的最长上升子序列的结尾的最大值

需要注意的是:为了选取最小的答案,我们需要从后往前选取最长下降子序列;
然后用pr[i] 在最长的前提下记录一下最小的转移到f[i]的位置j。
之后再选取一个最长的上升子序列输出就行了。

时间复杂度

参考文献

C++ 代码

#include<iostream>

using namespace std;

const int N = 1e5 + 10;
string s[N];

int f[N];
int pr[N];
int main()
{
    int cnt = 0;
    char c;
    while(cin>>c)
    {
        if(c>='A'&c<='Z') s[++cnt]=c;
        else s[cnt] += c;
    }

    for(int i = cnt; i >= 1; i--)
    {
        f[i] = 1;pr[i] = -1;
        for(int j = cnt; j > i; j--)
            if(s[i] < s[j])
            {
                if(f[i] == f[j] + 1)
                {
                    if(s[pr[i]] > s[j]) pr[i] = j;
                }
                else if(f[i] < f[j] + 1) 
                {
                    pr[i] = j;
                    f[i] = f[j] + 1;
                }
            }
    }
    int res = f[1], pos=1;
    for(int  i = 2; i <= cnt; i++)
        {
            if(f[i] > res) res=f[i], pos = i;
            else if(f[i] == res)
            {
                if(s[pos] > s[i]) pos = i;
            }
        }

        while(pos!=-1)
        {
            cout<<s[pos];
            pos = pr[pos];
        }

    return 0;
}

算法2

(暴力枚举) $O(n^2)$
  • 从贪心的角度来看
    其实也就是最长上升子序列的第二种写法。
    q[i] 记录不同长度的的最小的尾节点数值.

那么我们怎么输出最长上升子序列呢?
设f[i] 为a[i]结尾的最长上升子序列,此时长度为f[i]最长上升子序列结尾的最小值一定是a[i],
这是根据q[i]的性质决定的。
所以根据以上性质,我们最后的答案从后往前记录一定是从小到大的。

例如 f[5] = 4, f[6] = 4, 那么此时q[4] = a[6]一定是最小的。

时间复杂度

参考文献

C++ 代码

#include<iostream>

using namespace std;

const int N = 1e6 + 10;
string s[N],res[N];
string q[N];
int f[N];

int pr[N];
int main()
{
    int cnt = 0;
    char c;
    while(cin>>c)
    {
        if(c>='A'&&c<='Z') s[++cnt]=c;
        else s[cnt] += c;
    }



    q[0]="";
    int len = 0;
    for(int i = 1; i <= cnt; i++)
    {
         int l = 0, r = len; 
         while(l < r)
         {
             int mid = l + r + 1 >>1;
             if(q[mid] < s[i]) l = mid;
             else r = mid - 1;
         }
         f[i] = r + 1;
         len = max(len, f[i]);
         q[r+1] = s[i];
    } 

    int t = 0;

    for(int i = cnt; i >= 1; i--)
    {
            if(f[i] == len)
            {
                res[++t] = s[i];
                len--;
            }
    }
    for(int i = t; i >= 1; i--)
    {
        cout<<res[i];
    }


    return 0;
}



题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$

寻找数的范围这题是一个非常典型的例题:
求数的范围找到第一个大于等于这个数的位置,以及最后一个小于等于这个数的位置

  • 第一种寻找满足条件的第一个位置

模板为:
一般为递增的情况

while(l < r)
 {
     int mid = l + r >> 1;
     if(check(mid) // 表示满足条件
        r = mid;  //我们只需找第一个满足条件的所以要向左减少范围

     else 
        l = mid + 1; // 那么在l左边的一定不会满足条件

 }

以上可知在满足条件的时候,我们是向小的地方压缩范围, l左边的不会出现满足条件的
所以找到的是第一个满足条件的答案。

对于特殊情况:n = 2, a[1] = a[2] = true;在第一次二分的时候r = 1,便返回答案。

但是在这里我们不能写成 mid = l + r + 1;
这样会导致我刚刚枚举的是mid是右边界,然后下一次二分的时候如果又满足答案
那么右边界一直不变。

  • 找到满足条件的最后一个答案

模板

while(l < r)
 {
     int mid = l + r + 1 >> 1;
     if(check(mid) // 表示满足条件
        l = mid;  // 由于我们要找到最后一个满足条件的那么 就需要向右缩小范围

     else 
        r = mid - 1; // r的右边一定不会满足答案
 }

以上可知在满足条件的时候,我们是向大的地方压缩范围, r右边的不会出现满足条件的
所以找到的最后一个满足条件的答案

不过由于整除运算的特定会导致死循环:
:n = 2, a[1] = a[2] = true;
无论进行多少次二分 l = 1不变, 所以mid = l + r + 1 >> 1;
那么每次二分的时候总会优先右边那一个,如果这个满足条件则又会被当中左边界。
不会存在左边界一直不变的情况

时间复杂度

参考文献

C++ 代码

blablabla

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

blablabla



题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$
  • 不优化的情况


    设状态dp[i][j]:表示前i-1个骰子已经摆好,第i个骰子的下边为j的总数量

在状态转移的时候只需将骰子的上边和下边转换一下就行了
f[i][j] += f[i-1][k>3 ? k-3 : k+3];

最后可以过掉70%的数据,如果是比赛最好这样写,不容易出错。

  • 考虑矩阵优化

我们可以发现每次转移的方式是固定的

即 f[i-1][j] += f[i-1][j], k:与1没有冲突的点。

所以我们就可以将他们写成一个矩阵表达

f[i][1] = {a11 a12 a13 a14 a15 a16}  f[i-1][1]

f[i][2] = {a21 a22 a23 a24 a15 a26} f[i-1][2]

....
f[i][6] = {a61 a62 a63 a64 a65 a66} f[i-1][6]

aij: 表示i,j有无冲突

之后我们用矩阵快速幂求解n-1次方即可

C++ 代码

blablabla

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

#include<iostream>
#include<cstring>
using namespace std;

const int mod = 1e9 + 7;

typedef long long LL;

bool vis[10][10];

struct tmp
{
    LL x[7][7];
    tmp operator *(const tmp &a)
    {
        tmp t;
        for(int i = 1; i <= 6; i++)
        {
            for(int j = 1; j <= 6; j++)
            {
                t.x[i][j] = 0;
                for(int k = 1; k <= 6; k++)
                {
                 t.x[i][j] = (t.x[i][j] + x[i][k] * a.x[k][j] % mod)%mod;    
                }
            }
        }
        return t;
    }
};

LL qmi(int n)
{
    long long a = 4, res = 1;
    while(n)
    {
        if(n&1) res = res*a%mod;
        n >>= 1;
        a = a * a%mod;
    }
    return res;
}

tmp qmi1(int m)
{
        tmp a,res;

    for(int i = 1; i <= 6; i++)
    {    for(int j = 1; j <= 6; j++)
          {
                int t = j;
                if(t > 3) t-=3;
                else t+=3;
                if(vis[i][j]) a.x[i][t] = 1;
                else a.x[i][t] = 0;

                if(i==j)res.x[i][j] = 1; 
                else res.x[i][j] = 0;
               // cout<<a.x[i][j]<<" ";
          }

    }


    while(m)
    {
        if(m&1) res = res * a;
        m >>= 1;
        a = a * a;
    }


   return res;
}

int main()
{
    memset(vis,1,sizeof vis);
    int n,m;
    cin>>n>>m;

    for(int i = 1; i <= m; i++)
    {
        int a,b;
        cin>>a>>b;
        vis[a][b] = vis[b][a] = 0;
    }

     tmp res = qmi1(n-1);
    long long ans = 0;
        for(int i = 1; i <= 6; i++)
            for(int j = 1; j <= 6; j++)
                ans = (ans + res.x[i][j])%mod;            

    cout<<ans*qmi(n)%mod;


    return 0;
}



朱星星
10天前

题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$

这种题放在蓝桥杯是真滴好恶心啊,细节有点多,还是打暴力爽


  • 直接设状态dp[x][y][z]

我们发现这种的时间复杂度必然是nwm*cnt, cnt是质数的数量,肯定会爆掉的
但也可以拿到30%的暴力分


  • 考虑状态分割

我们发现在x方向走和y,z方向走的方式是一样的,我们只要记录dp[t],t=max(n,m,w)
然后dp[n],dp[m],dp[w],就都能只知道了

考虑怎么合并两维,若x走到n用i步,y走到m用j步, 那么我们总共就是有i+j步
那么总共方案数,就是从i+j个位置中选出i个位置给x, 剩下j个位置给y, 也就是组合数C[i+j][i];
因为选出的i个位置只有一种顺序,也就是从小到大的顺序。
(以上走i,j步是都只指某一种方案)

那么之后再把合并后的两维和其他一维再合并就好了
  • 分别设 X[i][j], XY[i],XYZ[i]:

    X[i][j]: 表示走道第i个位置,用了j步有多少种方案。
    XY[i]: 表示走到给定的二维位置(x, y)走了i步有多少种方案
    XYZ[i]: 表示走到给定的三维位置(x,y,z)走了i步有多少种方案

    容易想到这些转移方程
    x[i][j] += x[i - primes[k]][j - 1];
    XY[i + j] += X[n][i] * X[m][j] * C[i + j][j]
    XYZ[i + j] += X[w][i] * XY[j] * C[i + j][j]

  • 计算最终答案

    我们发现我们还应该减去那些经过特殊电的方案数,即(1,1,1)->(r1,c1,h1)->(n,m,2)和(1,1,1)->(r2,c2,h2)
    ->(n,m,w). 但是我们可能减去第一种方案数的时候又经过了(r2,c2,h2), 所以我们还要加上(1,1,1)->(r1,c1,h1)
    ->(r2,c2,h2)->(n,m,w).
    当然只有(r1, c2, h2)可以到达(r2, c2, h2),或者(r2, c2, h2)可以到达(r1, c1, h1)才需要计算
    
  • 最后但也同样重要的是 计算质数的时候 最好不要写成这样:primes[i] < N/i 会少一个质数,
    可以写成primes[j] <= 1000/i, 或者primes[j] <= n/i;

时间复杂度

参考文献

C++ 代码

#include<iostream>
#include<cstring>
using namespace std;

#define ll long long
const int N = 1010, M = N/2, mod = 1e9 + 7;
bool vis[N];
int primes[N], cnt;
int n,m,w;
long long X[N][M], Y[N][M], Z[N][M]; 
long long f[N+M][N+M];
long XY[N], XYZ[2*N];




void init()
{
    for(int i = 2; i < N; i++)
    {
        if(!vis[i]) primes[++cnt] = i;
        for(int j = 1; i <= 1000/primes[j]; j++ )
        {
            vis[i*primes[j]] = true;
            if(i%primes[j] == 0) break;
        }
    }

    for(int i = 0; i < N; i++) f[i][0] = 1;

    for(int i= 1; i < N+M; i++)
    {
        for(int j = 1; j <= i; j++)
        {
            f[i][j] = (f[i-1][j] + f[i-1][j-1])%mod;
        }
    }


   X[1][0]=1;
    for(int i=2;i< N;i++){
        for(int k=1;k<=(i-1)/2;k++){
            for(int j=1;j<=cnt&&primes[j]<i;j++){
                X[i][k]=(X[i][k]+X[i-primes[j]][k-1])%mod;
            }
        }
    }

}





ll solve(int r, int cc, int w){
    ll ans = 0;
    int sr = r / 2;  //走r行最多需要r/2次,每次走两行 
    int sc = cc / 2;
    int sw = w / 2;
    memset(XY, 0, sizeof(XY));
    for(int i = 0; i <= sr; i++){  //r行走几次 
        for(int j = 0; j <= sc; j++){  //c列走几次 
            XY[i + j] = (XY[i + j] + X[r][i] * X[cc][j] % mod * f[i + j][i] % mod) % mod;
//            cout<<dp2[i + j]<<endl; 
        }
    }
    for(int i = 0; i <= sr + sc; i++){
        for(int j = 0; j <= sw; j++){
            ans = (ans + XY[i] * X[w][j] % mod * f[i + j][j] % mod) % mod;
//            cout<<ans<<endl;
        }
    }
    return ans;
}

int main()
{
        init();

    cin>>n>>m>>w;
    int r1,l1,z1, r2,l2,z2;
    cin>>r1>>l1>>z1;
    cin>>r2>>l2>>z2;

    long long res = solve(n, m, w);

    res = (res - solve(r1, l1, z1)*solve(n - r1+1, m - l1+1, w - z1+1)%mod+ mod)%mod;

    res = (res - solve(r2, l2, z2)*solve(n - r2+1, m - l2+1, w - z2+1)%mod+ mod)%mod;

    if(r1 >= r2 && l1 >= l2 && z1 >= z2)
        res = (res + solve(r2, l2, z2) * solve(r1 - r2+1, l1 - l2+1, z1 - z2+1) % mod * solve(n - r1+1, m - l1+1, w - z1+1)%mod )% mod;
    else if(r1 <= r2 && l1 <= l2 && z1 <= z2)
        res = (res + solve(r1, l1, z1) * solve(r2 - r1+1, l2 - l1+1, z2 - z1+1) % mod * solve(n - r2+1, m - l2+1, w - z2+1)%mod )% mod;

    cout<<(res+mod)%mod;

    return 0;
}

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

blablabla



朱星星
12天前

题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$

1.首先 a^b^c = 0, 即c = a^b,所以我们可以通过枚举a和b的每一位,来确定c的每一位。


设(x,y)为a,b二进制中一位的取值

2.假设a最大,那么枚举(1,1), (1,0),(0,1),(0,0) __ :其中(1,1),(0,0)对a,b大小关系无影响那么只有(1,0)出现在(0,1)前面,a>b才能成立。


3.同时我们也要满足a > c, 那么枚举的(1,1)(a=1,c=0) 和(0,1)(a=0,c=1) 在顺序上,
(1,1)要出现再(0,1)前面

4.最后 c + b > a, 枚举(1,1),(1,0)时,a 和(b+c)平分一个1 所以 a = b + c,
那么只有(0,1)出现时 a < b + c才会满足。

综上,(1,1),(0,1),(1,0)必须全部出现,且(1,1)和(1,0)出现在(0,1)前面


状态定义

我们用状态压缩来表达选择的每一个状态,(1,1)==4, (1,0)==2, (0,1)==1

然后我们通过数位dp来求解问题,每次选择在一个位中选择两个
设dp[pos][limit][state]表示前pos位,有没有限制位limit, 当前选择的状态是state

如果设置成dp[pos][state]会超时,因为有限制位状态没记录。


状态转移

当这一位是0的时候,我们可以选择(0,0) 并且如果state>=6, 就可以选择(0,1)

(0,1)注:虽然这一位枚举是0, 但我们b依然可以选择1, 因为b < a, 它的二进制位是不受当前限制的

当这一位是1的时候,可以选择(1,1) 和(1,0)

时间复杂度

参考文献

C++ 代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
int n, a[40];
long long dp[40][2][20];// 代表的没有限制的
long long dfs(int pos, int lead, int limit, int state)
{
    if(pos==-1)  return state == 7; 

   if( dp[pos][limit][state]!=-1) return dp[pos][limit][state];

   long long  res = 0;
   int up = limit ? a[pos] : 1;
   for(int i = 0; i <= up; i++)
   {
       if(i == 0)
       {
           res += dfs(pos - 1, lead && !i, limit && i==up, state);//0, 0
           if(state >= 6)
           res += dfs(pos - 1, lead && !i, limit && i==up, 7);// 0, 1
       }
       else
       {
           res += dfs(pos - 1, lead && !i , limit && i == up, state | 2 );//1,0
           res += dfs(pos - 1, lead && !i , limit && i == up, state | 4 );//1,1
       }
   }
   return dp[pos][limit][state] = res;
}

void solve()
{
     memset(dp,-1,sizeof dp);
    cin>>n;
    int len = 0;
    for(int i = 0; i <= 31; i++)
    {
        a[i] = n >> i & 1;
        len = max(len, a[i]==1 ? i : 0);
    }
  long  long res = dfs(len, 1, 1, 0)*3;
    cout<<res<<'\n';


}

int main()
{

    int t;
    cin>>t;
    while(t--)
    {
       solve();
    }
    return 0;
}

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

blablabla



朱星星
14天前

题目描述

blablabla

样例

blablabla

算法1

(暴力枚举) $O(n^2)$
这个题的状态基本上就是依据题目意思设置就好了,设f[i][j]为前i个A字符,前j个B字符达到相等状态的最短的编译距离。

然后就是初始值的设置 了,考虑含0的这些状态,f[i][0]. f[0][j]。显然我们可以通过删除和添加操作使这些状态达到相等

一般地:
 对于操作1有 删除A第i个字符 那么 f[i-1][j] + 1 ->> f[i][j]
 对于操作2有 给在A中第i个后面添加一个字符 那么 f[i][j-1] + 1 - >> f[i][j]
 对于操作3有 替换A的第i个字符, f[i-1][j-1] + 1 ->> f[i][j]
 当 a[i]==b[j] 不需要替换字符, f[i-1][j-1] ->> f[i][j]


时间复杂度

参考文献

C++ 代码

blablabla

算法2

(暴力枚举) $O(n^2)$

blablabla

时间复杂度

参考文献

C++ 代码

#include<iostream>

using namespace std;
const int N = 1010;
char a[N], b[N];
int f[N][N];
int main()
{
    int n,m;
    cin>>n>>(a+1)>>m>>(b+1);


   for(int i = 0; i <= m; i++) f[0][i] = i;
   for(int i = 0; i <= n; i++) f[i][0] = i;

    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j  <= m; j++)
          {

            if(a[i]==b[j]) f[i][j] = f[i-1][j-1]; 
            else f[i][j] = f[i-1][j-1] + 1; // 表示替换

            f[i][j] = min(f[i][j], f[i][j-1] + 1); // 表示添加
            f[i][j] = min(f[i][j], f[i-1][j] + 1); // 表示删除
         }
    }

    cout<<f[n][m];

    return 0;
}