2023河南icpc省赛5/13

A

考虑固定左端点,右端点向右走的过程中集合不断变大,但是只会变大m次。所以大胆向右跑,复杂度nmlog

用int128存一下集合状态
牛客神机,没加优化的代码1.7秒就过了。。。赛场上这个代码还要优化

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read() {
    ll x;
    scanf("%lld",&x);
    return x;
}
set<__int128>ans;
queue<int>pos[200010];
priority_queue<pair<int,int> >o;
int n,m;
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
        for(int k=read();k;k--)
            pos[read()].push(i);
    for(int l=1;l<=n;l++)
    {
        for(int i=1;i<=m;i++)
        {
            if(pos[i].size()&&pos[i].front()<l)
                pos[i].pop();
            if(pos[i].size())
                o.push({-pos[i].front(),i});
        }
        __int128 now=0,t=1;
        int r;
        while(o.size())
        {
            r=o.top().first;
            while(o.size()&&o.top().first==r)
            {
                now=now|(t<<o.top().second);
                o.pop();
            }
            ans.insert(now);
        }
    }
    cout<<ans.size();
}
A

 

C

正常写的话就组合数搞一搞

但是不取模,那么问题就有趣起来了

众所周知,Σc(奇数,sum)=Σ(偶数,sum),是很对称的,所以我刚开始猜了每个点的贡献只有1或-1,试了几个菊花图后发现贡献和儿子数量有关:a[x]*(x的儿子数量-1)

找找规律:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read()
{
    ll x;scanf("%lld",&x);return x;
}
int fa[100],d[100],sum[100];
int n;
vector<int>e[100],a;
int lca(int x,int y)
{
    while(d[x]>d[y])
        x=fa[x];
    while(d[x]<d[y])
        y=fa[y];
    while(x!=y)
        x=fa[x],y=fa[y];
    return x;
}
void dfs(int x)
{
    for(auto y:e[x])
    {
        if(y==fa[x])continue;
        fa[y]=x;
        d[y]=d[x]+1;
        dfs(y);
    }
}
void work(int d)
{
    if(d==n+1)
    {
        if(a.size()==0)return ;
        int t=a[0];
        for(auto x:a)
            t=lca(x,t);
        if(a.size()&1)
            sum[t]++;
        else
            sum[t]--;
        return ;
    }
    a.push_back(d);
    work(d+1);
    a.pop_back();
    work(d+1); 
}
int main()
{
    n=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1);
    work(1);
    for(int i=1;i<=n;i++)
        cout<<sum[i]<<' ';
}
找规律

 

考虑ai对答案的贡献,是ai*以i为lca的点集方案,偶数方案数-奇数方案数)

考虑这样的点集,要么i出现+多个子树的点,要么i出现+一个子树的点,要么i没出现+多个子树的点,要么只有i自己一个

第一种情况和第三种情况一一消掉(因为奇偶性一定相反)

还剩下i出现+一个子树的点

众所周知,在这个子树里选点,出现奇数的方案数=出现偶数的方案数

但是呢,你把出现0次的单拎出来,它对答案的贡献是+1

 

最后算上只有i自己一个的-1的贡献,结果为儿子数量-1

 

可以ac的代码(大概:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read()
{
    ll x;scanf("%lld",&x);return x;
}
int n;
ll sum[200010],ans;
int main()
{
    n=read();
    for(int i=1;i<n;i++)
        sum[read()]++;
    for(int i=1;i<=n;i++)
        ans+=(sum[i]-1)*read();
    cout<<ans;
}
C

 

 K

大胆贪心

如果有正数也有负数,就输出Σ正数*Σ负数

如果只有正数就输出(sum-最小正数)*最小正数

如果只有负数就输出(sum-最大负数)*最大负数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read() {
    ll x;
    scanf("%lld",&x);
    return x;
}
int n;
ll a[200010],ans,sum0,sum1;
vector<ll>q[2];
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        if(x==0)
        {
            q[0].push_back(0);
            q[1].push_back(0);   
        }
        else if(x<0)
        {
            sum0+=x;
            q[0].push_back(x);
        }
        else
        {
            sum1+=x;
            q[1].push_back(x);
        }
    }
    sort(q[0].begin(),q[0].end());
    sort(q[1].begin(),q[1].end());
    if(q[0].size()&&q[1].size())
        ans=sum1*sum0;
    else if(q[0].size())
        ans=(sum0-q[0].back())*q[0].back();
    else
        ans=(sum1-q[1].front())*q[1].front();
    cout<<ans;
}
K

 

 L

二维前缀和板子题

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read() {
    ll x;
    scanf("%lld",&x);
    return x;
}
int n,m,k,sum[4][1010][1010];
int a,b,c,d;
char s[1010];
int main()
{
    n=read();m=read();k=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
        {
            sum[1][i][j]=sum[1][i-1][j]+sum[1][i][j-1]-sum[1][i-1][j-1]+(s[j]=='C');
            sum[2][i][j]=sum[2][i-1][j]+sum[2][i][j-1]-sum[2][i-1][j-1]+(s[j]=='M');
            sum[3][i][j]=sum[3][i-1][j]+sum[3][i][j-1]-sum[3][i-1][j-1]+(s[j]=='F');
        }
    }
    for(;k;k--)
    {
        a=read();b=read();c=read();d=read();
        for(int i=1;i<=3;i++)
            printf("%d ",sum[i][c][d]-sum[i][c][b-1]-sum[i][a-1][d]+sum[i][a-1][b-1]);
        printf("\n");
    }
}
L

 

 

M

理解题意后发现只需要对每个商家的物品从大到小排序,选前第i大的物品做背包,体积是i,假价值是(Σai)-X。f[i][j]表示前i个商家用j个物品最多卖多少钱。复杂度m*n+n*n

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read() {
    ll x;
    scanf("%lld",&x);
    return x;
}
int n,m,k;
int x[1010],f[1010][1010];
vector<int>a[1010];
int main()
{
    n=read();m=read();k=read();
    for(int i=1;i<=m;i++)
        x[i]=read();
    for(int i=1;i<=n;i++)
    {
        int v=read();
        a[read()].push_back(v);
    }
    for(int i=1;i<=k;i++)
        f[0][i]=-1e9;
    for(int i=1;i<=m;i++)
    {
        for(int kk=1;kk<=k;kk++)
            f[i][kk]=f[i-1][kk];
        sort(a[i].begin(),a[i].end(),greater<int>());
        for(int j=0,cnt=1,sum=0;j<a[i].size();j++,cnt++)
        {
            sum+=a[i][j];
            for(int kk=k;kk>=cnt;kk--)
                f[i][kk]=max(f[i][kk],f[i-1][kk-cnt]+sum-x[i]);
        }
    }
    cout<<f[m][k];
}
M

 

posted @ 2023-05-22 18:24  zzuqy  阅读(468)  评论(0编辑  收藏  举报