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 异或前缀和,思维,递推

 2023牛客暑期多校训练营5_第1张图片

设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
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]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(lnowl)
       {
           l--;
           addL(l);
       }

       while(rnowr)
       {
           delR(r);
           r--;
       }

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


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

 

2023牛客暑期多校训练营5_第2张图片

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

#include 
using namespace std;
typedef long long int ll;
ll a[1010],k;
int n;
priority_queuepre,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<

 2023牛客暑期多校训练营5_第3张图片

 

 

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

#include 
using namespace std;
typedef long long int ll;

int a[3030][3030];
sets[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]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<

 

 2023牛客暑期多校训练营5_第4张图片

数学签到 

 

# include
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<

 

2023牛客暑期多校训练营5_第5张图片

 2023牛客暑期多校训练营5_第6张图片

 

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

 

#include 
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.lenv[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

 2023牛客暑期多校训练营5_第7张图片

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

# include
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<

2023牛客暑期多校训练营5_第8张图片

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

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

#include 
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 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]);
}

 2023牛客暑期多校训练营5_第9张图片

 

 

 先做异或和。对于每一位进行考虑,遍历1到n,当前二进制pos位置,他的贡献为(1<

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

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

#include 
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<

你可能感兴趣的:(多校真题,算法,区域赛,ICPC)