2019杭电多校第三场

1004 Distribution of books

首先应该很容易猜到要二分答案
那么如何check这个上限x?
f[i]f[i]f[i]表示前iii本书最多能分成的多少堆
那么有f[i]=max{f[j]}+1f[i]=max\{f[j]\}+1f[i]=max{f[j]}+1,其中jjj要保证sum[i]−sum[j]&lt;=xsum[i]-sum[j]&lt;=xsum[i]sum[j]<=x
提前对前缀和离散化并且建立线段树
二分sum[j]sum[j]sum[j]最大可以取到多少,然后就可以查询区间最大值了

1006 Fansblog

首先由质数的密度分布可以大力猜测这个质数QQQPPP应该不会很远,所以暴力从大到小枚举就好
判断是不是质数可以用MillerRobinMiller RobinMillerRobin测试
然后用一下威尔逊定理:当且仅当ppp是质数时,(p−1)!≡−1(mod(p-1)!\equiv -1(mod(p1)!1(mod p)p)p)
所以用逆元搞一下就好了

1007 Find the answer

实际上不需要什么骚操作,暴力找出若干个最大的变成0就好
可以用线段树做,线段树里从大到小存了前缀
每次判断所有数的和减去左半区间的数后时候是否依然大于mmm
如果大于说明需要继续变成0,减一减继续往右区间递归
否则说明已经符合要求,往左递归
当然似乎并没有卡掉两个log的做法:二分位置然后线段树直接查询前缀和

#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 300000
#define ll long long
using namespace std;
struct www{int a,i;} f[N];
int T,n,m,i,res;
ll s;
int a[N],c[N];
long long sum[N*4];
int num[N*4];
bool cmp(const www &x,const www &y) {return x.a > y.a;}
void build(int rt,int l,int r)
{
    sum[rt] = num[rt] = 0;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
}
void query(int rt,int l,int r,ll s)
{
    if (l == r)
    {
        res+=num[rt];
        return;
    }
    int mid = (l + r) >> 1;
    if (s - sum[rt<<1] <= m)
        query(rt<<1,l,mid,s);
    else
        {
            res += num[rt<<1];
            query(rt<<1|1,mid+1,r,s-sum[rt<<1]);
        }
}
void update(int rt,int l,int r,int pos,ll v)
{
    if (l == r)
    {
        sum[rt] = v; num[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid) update(rt<<1,l,mid,pos,v);
            else    update(rt<<1|1,mid+1,r,pos,v);
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    num[rt] = num[rt<<1] + num[rt<<1|1];
}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&n,&m);
        fo(i,1,n) scanf("%d",&a[i]);
        fo(i,1,n) f[i].a = a[i];
        fo(i,1,n) f[i].i = i;
        sort(f+1,f+n+1,cmp);
        fo(i,1,n) c[f[i].i] = i;
        s = 0;
        build(1,1,n);
        fo(i,1,n)
        {
            s = s + a[i]; res = 0;
            query(1,1,n,s);
            printf("%d ",res);
            update(1,1,n,c[i],a[i]); 
        }
        printf("\n");
    }
    return 0;
}

1009 K subsequence

费用流
只能取kkk次:源点sss拆成s0s_0s0s1s_1s1,之间的流量为kkk
每个点只能取一次:xix_ixi拆成xi1x_{i1}xi1xi2x_{i2}xi2,之间的流量为1
和最大:xi1x_{i1}xi1xi2x_{i2}xi2之间的费用为−ai-a_iai
连边:若i&lt;ji&lt;ji<jai≤aja_i \le a_jaiaj,说明aja_jaj可以跟在aia_iai后面,所以在xi2x_{i2}xi2xj1x_{j1}xj1之间连边,流量为1
然后将s1s_1s1和所有的xi1x_{i1}xi1连起来,流量为1,表示每个点都有可能成为某个子序列的开头
同时将ttt和所有的xi2x_{i2}xi2连起来
s0s_0s0ttt跑一次最小费用最大流并且取反就是答案了
PS:出题人卡掉了SPFA的板子。。
我的做法是每次往前连边的时候暴力维护KKK个最大值,如果当前的点小于这KKK个最大值就不要连边了(哦显然这个做法应该是假的,不过主要目的还是为了边连的少一点)
听说有人暴力连K2K^2K2条边都过了。。。
这题适合乱搞

#include <bits/stdc++.h>
using namespace std;
const int maxn=6e3+5;
typedef long long ll;
struct Edge
{
    int from,to,cap,flow,cost;
    Edge(int from,int to,int cap,int flow,int cost):from(from),to(to),cap(cap),flow(flow),cost(cost){}
};
const int INF=2e8;
vector <Edge> edge;
vector <int> g[maxn];
int a[maxn],p[maxn],c[maxn],inq[maxn];
void init(int n)
{
    edge.clear();
    for (int i=0;i<=n;i++)
        g[i].clear();
} 
void addedge(int from,int to,int cap,int cost)
{
    edge.push_back(Edge{from,to,cap,0,cost});
    edge.push_back(Edge{to,from,0,0,-cost});
    int m=edge.size();
    g[from].push_back(m-2);
    g[to].push_back(m-1);
} 
bool bellmanford(int s,int t,int limit_flow,int &flow,ll &cost)
{
    memset(a,0,sizeof(a));
    memset(c,0x3f,sizeof(c));
    memset(inq,0,sizeof(inq));
    queue<int> q;
    a[s]=INF;
    c[s]=0;
    inq[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int x=q.front();
        inq[x]=0;
        q.pop();
        for (int i=0;i<g[x].size();i++)
        {
            Edge &e=edge[g[x][i]];
            if (e.cap>e.flow && c[e.to]>c[x]+e.cost)
            {
                c[e.to]=c[x]+e.cost;
                p[e.to]=g[x][i];
                a[e.to]=min(a[x],e.cap-e.flow);
                if (!inq[e.to])
                {
                    q.push(e.to);
                    inq[e.to]=1;
                }
            } 
        }
    }
    if (c[t]>=INF) return false;
    if (a[t]+flow>limit_flow) a[t]=limit_flow-flow;
    for (int i=t;i!=s;i=edge[p[i]].from)
    {
        edge[p[i]].flow+=a[t];
        edge[p[i]^1].flow-=a[t];
    }
    flow+=a[t];
    cost+=(ll)c[t]*a[t];
    return true; 
}
int MCMF(int s,int t,int limit_flow,ll &cost)
{
    int flow=0;
    cost=0;
    while (flow<limit_flow && bellmanford(s,t,limit_flow,flow,cost));
    return flow;
}

void build(int n,int k)
{
    init(2*n+3);
    int ss=0,s=2*n+1,t=2*n+2;
    for (int j=1;j<=n;j++)
    {
        int cnt=0;
        priority_queue<int,vector<int>,less<int> > q;//神奇的精髓部分。。。
        for (int i=j-1;i>=1;i--)
            if (a[i]<=a[j])
                {
                    if (cnt <= k) {addedge(2*i,2*j-1,1,0); q.push(a[i]); cnt++;}
                    else 
                    if (a[i] > q.top())
                        {
                            addedge(2*i,2*j-1,1,0);
                            q.push(a[i]);
                            q.pop();
                        }
                }
    }
        
    for (int i=1;i<=n;i++)
        addedge(2*i-1,2*i,1,-a[i]);
    for (int i=1;i<=n;i++)
        addedge(s,2*i-1,1,0);
    addedge(ss,s,k,0);
    for (int i=1;i<=n;i++)
        addedge(2*i,t,1,0);
    ll cost;
    MCMF(ss,t,INF,cost);
    printf("%lld\n",-cost);
}
int main()
{
    //freopen("1.in","r",stdin);
    int t,n,k;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d",&n,&k);
        //cout<<n<<' '<<k<<endl;
        for (int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        build(n,k);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值