2023牛客暑期多校训练营5

Jujubesister 莫队 前缀和

Circle of Mistery 构造+ [对顶堆贪心]

Cheeeeen the Cute Cat 贪心

Cirno's Perfect Equation Class 签到数学

Red and Blue and Green 构造 ,递归 ,树

Go to Play Maimai DX 二分,签到

Nazrin the Greeeeeedy Mouse  DP

The Yakumo Family 异或前缀和,思维,递推

 

设cnt[i]为i之前小于a[i]的数字个数 。而一个位置j,a[j]=a[i]时,j与i的贡献是 cnt[i]-cnt[j]

考虑用莫队来维护,采用数形结合方法更容易推出。

加入右端点后一位的时候,设为a[i],则a[i]的贡献为图中红线减去黑线,也就是 区间与a[i]值相同的点数目*cnt[i] - sum,其中sum是a[i]的cnt之和,也就是图中黑线长度之和。

其余删除或者加入左端点右端点,也是这样推出。

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

typedef long long int  ll;

struct node
{
    int l,r,id;
};
int belong[500000+10];
struct node s[500000+10];
ll a[500000+10],pre[500000+10],cnt[500000+10],sum[500000+10];
ll tree[500000+10];
int n,m;
int lowbit(int x)
{
    return x&-x;
}
void change(int x)
{
    while(x<=n)
    {
        tree[x]++;
        x+=lowbit(x);
    }
}
int getsum(int x)
{
    int ans=0;
    while(x)
    {
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}
bool cmp(struct node x, struct node y)
{
    if(belong[x.l]!=belong[y.l])
        return belong[x.l]<belong[y.l];
    if(belong[x.l]&1)
        return x.r<y.r;
        return x.r>y.r;
}
ll ans=0;

void delL(int pos)
{
    ans-=(sum[a[pos]]-cnt[a[pos]]*pre[pos]);
    sum[a[pos]]-=pre[pos];
    cnt[a[pos]]--;
}
void delR(int pos)
{
    ans-=(cnt[a[pos]]*pre[pos]-sum[a[pos]]);
    sum[a[pos]]-=pre[pos];
    cnt[a[pos]]--;
}
void addL(int pos)
{
    ans+=(sum[a[pos]]-cnt[a[pos]]*pre[pos]);
    sum[a[pos]]+=pre[pos];
    cnt[a[pos]]++;

}
void addR(int pos)
{
    ans+=(cnt[a[pos]]*pre[pos]-sum[a[pos]]);
    sum[a[pos]]+=pre[pos];
    cnt[a[pos]]++;

}
ll fuck[500000+10];

int main ()
{
   cin.tie(0);
   ios::sync_with_stdio(0);

   cin>>n>>m;

   for(int i=1;i<=n;i++)
   {
       cin>>a[i];
       pre[i]=getsum(a[i]-1);
       change(a[i]);
   }

   int siz=sqrt(n);
   int len=ceil((double)(n/siz));

   for(int i=1;i<=len;i++)
   {
       for(int j=(i-1)*siz+1;j<=i*siz;j++)
       {
           belong[j]=i;
       }
   }
   for(int i=1;i<=m;i++)
   {
       cin>>s[i].l>>s[i].r;
       s[i].id=i;
   }
   sort(s+1,s+1+m,cmp);

   int l=1,r=0;

   for(int i=1;i<=m;i++)
   {
       int nowl=s[i].l,nowr=s[i].r;

       while(l<nowl)
       {
           delL(l);
           l++;
       }

       while(l>nowl)
       {
           l--;
           addL(l);
       }

       while(r<nowr)
       {
           r++;
           addR(r);
       }

       while(r>nowr)
       {
           delR(r);
           r--;
       }

       fuck[s[i].id]=ans;
   }


   for(int i=1;i<=m;i++)
   {
       cout<<fuck[i]<<'\n';
   }
    return 0;

}

 

 首先假定只有一个置换环,使得逆序对最少的话。考虑如 1 2 3 4 5 ,转化为 2 3 4 5 1。而一个置换环不一定数字连续,假设我们选种 b1,b2,,,,,bk其中b1最小,bk最大,不难得出,为了让逆序对最少,b1到bk区间外的数字保持不变,内部新产生的逆序对的个数为2*(bk-b1)-k+1其中。于是,可以暴力枚举起点,贪心的想,k个数字之和要大于给定约束,所以我们遇到正数是必须要选的。一个负数,如果在末尾添加进去,不会使答案更优。维护一个对顶堆,大根堆存的是,尚未加入的负数,小跟堆存的是已经使用的负数的相反数。正数直接累加入答案。当前新的负数,如果能比已加入的更优,显然淘汰更劣的。

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[1010],k;
int n;
priority_queue<ll>pre,temp;
void init()
{
    while(!pre.empty())
        pre.pop();
    while(!temp.empty())
        temp.pop();
}
int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
   ll ans=1e18;
    for(int i=1;i<=n;i++)
    {
        init();
        ll nowsum=0;
        int nowcnt=0;
        nowsum+=a[i];
        if(a[i]<0)
            pre.push(-a[i]);
        nowcnt++;
        if(nowsum>=k)
            ans=min(ans,0ll);
        for(int j=i+1;j<=n;j++)
        {
            if(a[j]>=0)
            {
                nowsum+=a[j];
                nowcnt++;
                while(!temp.empty()&&nowsum+temp.top()>=k)
                {
                    nowsum+=temp.top();
                    pre.push(-temp.top());
                    nowcnt++;
                    temp.pop();
                }
                if(nowsum>=k)
                {
                    ans=min(ans,2ll*(j-i)-nowcnt+1);
                }
            }
            else
            {

                temp.push(a[j]);
                while(!pre.empty()&&!temp.empty()&&temp.top()>-pre.top())
                {
                    ll nowtemp=temp.top();
                    ll nowpre=pre.top();
                    temp.pop();
                    pre.pop();
                    nowsum-=(-nowpre);
                    nowsum+=(nowtemp);
                }
            }
        }
    }

        if(ans==1e18)
        {
            cout<<-1<<'\n';
        }
        else
        {
            cout<<ans<<'\n';
        }


    return 0;
}

 

 

 

直接贪心,每次从右集合度数最小的点里面,找到他连接的度数最小的左集合的点,二者相消。模拟一下就行,没有用到图论知识。 

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

int a[3030][3030];
set<int>s[6060];
int du[3030*2];
int n;
int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

void write(int x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
int main()
{
    int n;
    n=read();
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            int x;
            x=read();
            a[i][j]=x;
            if(x==1)
            {
                du[i]++;
                du[j+n]++;
            }
        }
    }
    int cnt=0;
    while(1)
    {
        int pos=0;
        int minn=1e9;
        for(int i=n+1; i<=2*n; i++)
        {
            if(du[i]>0&&du[i]<minn)
            {
                pos=i;
                minn=du[i];
            }
        }
        if(minn==1e9)
            break;
        int fuck=0;
        minn=1e9;
        for(int i=1;i<=n;i++)
        {
            if(a[i][pos-n])
            {
                if(du[i]>0&&minn>du[i])
                {
                    minn=du[i];
                    fuck=i;
                }
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(a[i][pos-n])
            {
                du[i]--;

            }
            if(a[fuck][i+n])
            {
                du[i+n]--;
            }
        }
        du[pos]=0;
        du[fuck]=0;
        if(pos&&fuck)
        cnt++;
    }
    cout<<cnt;
    return 0;
}

 

 

数学签到 

 

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

int main ()
{
   int t;
   cin>>t;
   while(t--)
   {
       int k,c,n;
       scanf("%d%d%d",&k,&c,&n);
       int cnt=0;
       for(int i=1;i*i<=c;i++)
       {
           if(c%i==0)
           {
               int fac1=i,fac2=c/i;
               int a,b;

               b=fac1;
               if((c-b)%k==0)
               {
                   a=(c-b)/k;
                   if(__gcd(a,b)>=n&&a&&b)
                   {
                       cnt++;
                   }
               }
               if(fac1==fac2)
                continue;
               swap(fac1,fac2);
               b=fac1;
               if((c-b)%k==0)
               {
                   a=(c-b)/k;
                   if(__gcd(a,b)>=n&&a&&b)
                   {
                       cnt++;
                      
                   }
               }
           }
       }
       cout<<cnt<<'\n';
   }
    return 0;
}

 

 

 

这题应该来说是一道技巧性很强的题目,处理数据稍微麻烦一些,就会有很多特判和细节。采用题解存图的方式,一层一层存包含关系。当前大区间的若干小区间变换之后,仍不满足当前大区间需要时,考虑交换一个子区间的右端点和其右端点加1位置。即上图A1,A2的交换。但这样还是不够的。如果遇到上图第一个的情况,即A3,A4在交换之后,如果再交换一次A5,A3逆序对就波及了其他区间。可见,不能简单的交换一个子区间的右端点当前值。而是在整个区间,找到这一值所在位置即A4所在位置和A5所在位置,进行交换。这样交换一次。由于值只相差1,且只是交换了相互位置,故对于其他点和区间没有任何影响。可以看做,“没有交换,但改变了逆序对” 。这应当属于构造题。

 

#include <bits/stdc++.h>
typedef long long int ll;
using namespace std;
struct node
{
    int len,l,r,val;
};
struct node s[10000+10];
bool cmp(struct node x, struct node y)
{
    return x.len<y.len;
}
int book[1010][1010];
vector<int>v[5050];
int vis[5050],du[5050],ans[5050];
int main()
{
    int n,m;
    cin>>n>>m;
    memset(book,-1,sizeof(book));int len=0;
    for(int i=1;i<=m;i++)
    {
        int x,y,val;
        cin>>x>>y>>val;
        if(x==y&&val)
        {
            cout<<-1;
            return 0;
        }
        if(book[x][y]!=-1)
        {
            if(book[x][y]!=val)
            {
                cout<<-1;
                return 0;
            }
        }
        else
        {
            book[x][y]=val;
            len++;
            s[len].len=y-x+1;
            s[len].l=x;
            s[len].r=y;
            s[len].val=val;
        }
    }
    for(int i=1;i<=n;i++)
    {
        ans[i]=i;
        if(book[i][i]==-1)
        {
            len++;
            s[len].len=1;
            s[len].l=i;s[len].r=i;s[len].val=0;
        }
    }
    sort(s+1,s+1+len,cmp);
    for(int i=1;i<=len;i++)
    {

        for(int j=1;j<i;j++)
        {
            if(vis[j])
                continue;
            if(s[i].l<=s[j].l&&s[j].r<=s[i].r)
            {
                vis[j]=1;
                v[i].push_back(j);
                du[j]++;
            }
        }
    }

    for(int i=1;i<=len;i++)
    {
        
            int l=s[i].l,r=s[i].r,val=s[i].val;
            for(auto it:v[i])
            {
                val^=s[it].val;
            }
            if(val==0)
                continue;
            for(auto it:v[i])
            {
                if(s[it].r!=r)
                {
                    int pos1=0,pos2=0;
                    for(int j=l;j<=r;j++)
                    {
                        if(ans[j]==s[it].r)
                        {
                            pos1=j;
                        }
                        if(ans[j]==s[it].r+1)
                        {
                            pos2=j;
                        }
                    }
                    swap(ans[pos1],ans[pos2]);
                    break;
                }
            }
        
    }

    for(int i=1;i<=n;i++)
    {
        cout<<ans[i]<<" ";
    }
    return 0;
}

 

满足二分性质,故直接二分 

# include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int sum[1000000+10][5];
int a[100000+10],n,m;
bool check(int x,int y)
{
    int flag1= sum[x][1]-sum[y-1][1]>=1;
    int flag2= sum[x][2]-sum[y-1][2]>=1;
    int flag3= sum[x][3]-sum[y-1][3]>=1;
    int flag4= sum[x][4]-sum[y-1][4]>=m;
    return flag1&&flag2&&flag3&&flag4;
}
int main ()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        for(int j=1; j<=4; j++)
        {
            sum[i][j]=sum[i-1][j]+(a[i]==j);
        }
    }
    int ans=1e9;
    for(int i=1; i+4-1<=n; i++)
    {
        int l=i,r=n;

        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid,i))
                r=mid-1;
            else
                l=mid+1;
        }
        if(l-i+1>=4&&check(l,i))
        {
            ans=min(ans,l-i+1);
        }
    }
    cout<<ans;
    return 0;
}

 令dp[i][j][k]为[i,j]区间,花费k的容量,获得的最大价值。当固定i端点的时候,右端点具有明显的单调性,即同一容量的可以继续承接,这一复杂度为n*n*sz。

而sz具有单调性,这一性质就降低了题目难度,只需要选取后200个就够了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 300;
ll sum[N][N][N];
ll a[N], b[N], dp[N];
ll dp0[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld%lld", &a[i], &b[i]);
    }

    for (int i = 1; i <= n; i++)
    {
        memset(dp0, 0, sizeof dp0);
        for (int j = i; j <= n; j++)
        {
            for (int k = 200; k >= a[j]; k--)
            {
                dp0[k] = max(dp0[k], dp0[k - a[j]] + b[j]);
                sum[k][i][j] = dp0[k];
            }
            for (int k = a[j]; k > 0; k--)
            {
                sum[k][i][j] = dp0[k];
            }
        }
    }
    deque<int> xs;
    while (m--)
    {
        int x;
        scanf("%d", &x);
        xs.push_back(x);
    }
    while (xs.size() > 300)
    {
        xs.pop_front();
    }
    while (!xs.empty())
    {
        int x = xs.front();
        xs.pop_front();
        for (int i = n; i > 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                dp[i] = max(dp[i], dp[j - 1] + sum[x][j][i]);
            }
        }
    }
    printf("%lld\n", dp[n]);
}

 

 

 

 先做异或和。对于每一位进行考虑,遍历1到n,当前二进制pos位置,他的贡献为(1<<pos),当且仅当之前的异或和有和这一位置pos的不同的01,这样我们就获得了以位置i二进制位置pos的贡献,对其再做前缀和,就获得了XOR(l1,r1)。

然后和第一部分一样,XOR(l2,r2)首先应该有值,不为0,才能利用预处理出的XOR(l1,r1)进行计算。故和第一步同理,必须确定以位置i二进制位置pos的贡献,他的前提也是,之前某个位置的01和pos位置不同,故在0位置和1位置上分别累加之前的前缀和。

这样做可以实现多次区间的异或和乘积,不止三次。

#include <bits/stdc++.h>
typedef long long int ll;
# define mod  998244353
using namespace std;

ll a[200000+10],sum[200000+10],cnt[2],Xor[200000+10],f[200000+10];
int main()
{

    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        sum[i]=a[i]^sum[i-1];
    }

    for(int i=1;i<=n;i++)
    {
        Xor[i]=1;
    }
    for(int ii=1;ii<=3;ii++)
    {
        for(int j=0;j<=30;j++)
        {
            cnt[0]=cnt[1]=0;
            if(ii==1)
                cnt[0]=1;
            for(int i=1;i<=n;i++)
            {
                int temp=0;
                if((sum[i]&(1<<j)))
                    temp=1;
                f[i]=(f[i]+cnt[temp^1]*(1ll<<j)%mod)%mod;
                cnt[temp]=(cnt[temp]+Xor[i])%mod;
            }
        }


        for(int i=1;i<=n;i++)
        {
            Xor[i]=(Xor[i-1]+f[i])%mod;
            f[i]=0;
        }
    }

    cout<<Xor[n];

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qinsanma and Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值