「JOI 2017/2018 决赛」题解

LOJ 2347~2351
BZOJ上只有其中2道: 4273,4279

寒冬暖炉
dp可以推个柿子把转移优化到 O(1) O ( 1 ) ,再套个wqs二分把状态数优化到 O(nlogn) O ( n l o g n )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define ld long double
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 210000;
const ld eps = 1e-12;

int n,K;
int a[maxn],b[maxn],ti[maxn];

struct node
{
    int k; ld x;
}f[maxn];
int dp(ld mid)
{
    f[0]=(node){0,0.0};
    int nown=0;
    for(int i=1;i<=n;i++)
    {
        f[i].k=f[nown].k+1;
        f[i].x=f[nown].x-b[nown]+(ld)a[i]+mid;
        if(f[i].x-b[i]<f[nown].x-b[nown]) nown=i;
    }
    return f[n].k;
}

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    read(n); read(K);
    for(int i=1;i<=n;i++) read(ti[i]);
    for(int i=0;i<=n;i++) a[i]=ti[i]+1,b[i]=ti[i+1];

    ld l=-ti[n],r=ti[n];
    while(r-l>eps)
    {
        ld mid=(l+r)/2.0;
        int k=dp(mid);
        if(k==K) { l=mid;break; }
        if(k<K) r=mid;
        else l=mid;
    }
    printf("%.0Lf\n",f[n].x-l*K);

    return 0;
}

美术展览
从小到大枚举Amax,最优的Amin可以 O(1) O ( 1 ) 维护

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

void read(ll &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10ll)+=c-'0';
}
const int maxn = 510000;

int n;
struct node
{
    ll a,b;
    friend inline bool operator <(const node x,const node y){return x.a<y.a;}
}a[maxn];

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    scanf("%d",&n);
    for(int i=1;i<=n;i++) read(a[i].a),read(a[i].b);
    sort(a+1,a+n+1);

    ll sum=0,pn=a[1].a,ans=0;
    for(int i=1;i<=n;i++)
    {
        sum+=a[i].b;
        ans=max(ans,pn-a[i].a+sum);
        pn=max(pn,a[i+1].a-sum);
    }
    printf("%lld\n",ans);

    return 0;
}

团子制作

将RGB定义为012,将一串团子在1处计数
发现不在同一对角线的1,他们串的团子不可能相交,同一对角线只有相邻的1有可能相交,对每一个对角线做一个dp,复杂度是线性的

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void up(int &a,const int &b){if(a<b)a=b;}
const int maxn =  3100;

int n,m,ans;
int a[maxn][maxn];
int Matchl(int i,int j)
{
    return a[i][j-1]==0&&a[i][j]==1&&a[i][j+1]==2;
}
int Matchc(int i,int j)
{
    return a[i-1][j]==0&&a[i][j]==1&&a[i+1][j]==2;
}
int f[2][3];

char str[maxn];

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    memset(a,-1,sizeof a);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",str+1);
        for(int j=1;j<=m;j++) a[i][j]=str[j]=='R'?0:(str[j]=='G'?1:2);
    }

    int ans=0;
    for(int s=2;s<=n+m;s++)
    {
        memset(f,0,sizeof f); int now=0;
        for(int j=max(1,s-n);j<=m&&j+1<=s;j++)
        {
            int i=s-j;
            now=!now;
            for(int l=0;l<3;l++)
            {
                int &temp=f[!now][l];
                up(f[now][0],temp);
                if(l!=2&&Matchl(i,j)) up(f[now][1],temp+1);
                if(l!=1&&Matchc(i,j)) up(f[now][2],temp+1);
                temp=0;
            }
        }
        up(f[now][0],f[now][1]);
        up(f[now][0],f[now][2]);
        ans+=f[now][0];
    }
    printf("%d\n",ans);

    return 0;
}

月票购买

一开始不会做,想了想部分分发现好像会了qaq
不难证明最终答案里U到V的最短路径,和S到T的月票线路只会有一段相交
枚举相交那一段的方向,也就是S->T和T->S两个方向,给每条有可能在ST最短路上的边定向,规定如果这条边要走0费用,一定要沿这个方向走
然后每个点拆3个点:还没相交,在相交段内,已过了相交段,跑Dijskral

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 210000;
const int maxm = 410000;

ll ans;
int n,m,S,T,U,V;
int e[maxm][3],ec[maxm];
struct Graph
{
    struct edge{int y,c,i,nex;}a[maxm]; int len,fir[maxn];
    inline void ins(const int x,const int y,const int c,const int i)
    {
        a[++len]=(edge){y,c,i,fir[x]};fir[x]=len;
    }

    struct node
    {
        ll x; int i;
        friend inline bool operator <(const node &x,const node &y){return x.x>y.x;}
    }; priority_queue<node>q;

    ll dis1[maxn],dis2[maxn],d[maxn<<1];
    void Dij(int st,ll dis[])
    {
        for(int i=1;i<=n;i++) dis[i]=LLONG_MAX;
        dis[st]=0; q.push((node){0ll,st});
        while(!q.empty())
        {
            const node now=q.top(); q.pop();
            int x=now.i; if(dis[x]!=now.x) continue;
            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(dis[y]>dis[x]+a[k].c)
                dis[y]=dis[x]+a[k].c,q.push((node){dis[y],y});
        }
    }
    void solve(int st,ll D,ll d1[],ll d2[])
    {
        for(int i=1;i<=m;i++) ec[i]=-1;
        for(int x=1;x<=n;x++)
        {
            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(d1[x]<d1[y]&&d1[x]+a[k].c+d2[y]==D)
                ec[a[k].i]=x<y;
        }

        for(int i=1;i<=n*3;i++) d[i]=LLONG_MAX;
        d[U]=0; q.push((node){d[U],U});
        while(!q.empty())
        {
            const node now=q.top(); q.pop();
            int x=now.i; if(d[x]!=now.x) continue;
            int use=(x-1)/n; x=(x-1)%n+1;

            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
            {
                if(use!=2&&ec[a[k].i]==(x<y))
                {
                    if(d[y+n]>now.x) d[y+n]=now.x,q.push((node){d[y+n],y+n});
                }
                int nu=use==1?2:use;
                if(d[y+nu*n]>now.x+a[k].c)
                    d[y+nu*n]=now.x+a[k].c,q.push((node){d[y+nu*n],y+nu*n});
            }
        }
        for(int i=0;i<3;i++) ans=min(ans,d[i*n+V]);
    }
}g;
void Solve()
{
    ans=LLONG_MAX;
    g.solve(S,g.dis1[T],g.dis1,g.dis2);
    g.solve(T,g.dis2[S],g.dis2,g.dis1);
}

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    read(n); read(m);
    read(S); read(T);
    read(U); read(V);
    for(int i=1;i<=m;i++)
    {
        int x,y,c; read(x),read(y),read(c);
        g.ins(x,y,c,i); g.ins(y,x,c,i);
        e[i][0]=x,e[i][1]=y,e[i][2]=c;
    }
    g.Dij(S,g.dis1); g.Dij(T,g.dis2);

    Solve();
    printf("%lld\n",ans);

    return 0;
}

毒蛇越狱

关于通配符的处理,一种方法是用子集和处理,但用子集和有个问题,就是询问串中要求为1的位置可能会算到0的情况,我们可以套一个容斥,但这个容斥的复杂度是单次 O(2L) O ( 2 L ) 的,太高了无法接受
观察发现1的个数,0的个数,?的个数最小值<=6,我们可以分三类,1的个数<=6时做子集和的容斥,0的个数<=6的时候,类似1的,我们可以另外做一个反向的子集和然后容斥,?的个数<=6时我们可以直接枚举所有匹配的情况统计和

总复杂度 O(2L+26q) O ( 2 L + 2 6 q )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define cal(x) __builtin_popcount(x)
using namespace std;

const int maxn = (1<<20)+20;

int n,m,al;
int s[maxn],f1[maxn],f0[maxn];

char str[maxn];

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    scanf("%d%d",&n,&m); al=1<<n;
    scanf("%s",str);
    for(int i=0;i<al;i++) f1[i]=f0[i]=s[i]=str[i]-'0';

    for(int i=0;i<n;i++)
        for(int t=1<<i,j=0;j<al;j++) if(!(j>>i&1))
        {
            f1[j+t]+=f1[j];
            f0[j]+=f0[j+t];
        }

    while(m--)
    {
        scanf("%s",str);
        int x=0,y=0,z=0;
        for(int i=0;i<n;i++) str[n-i-1]=='1'?x|=1<<i:(str[n-i-1]=='0'?y|=1<<i:z|=1<<i);

        int ans=0;
        if(cal(x)<=6)
        {
            for(int i=x;;i=(i-1)&x)
            {
                (cal(x^i)&1)?ans-=f1[i|z]:ans+=f1[i|z];
                if(!i) break;
            }
        }
        else if(cal(y)<=6)
        {
            for(int i=y;;i=(i-1)&y)
            {
                (cal(i)&1)?ans-=f0[i|x]:ans+=f0[i|x];
                if(!i) break;
            }
        }
        else
        {
            for(int i=z;;i=(i-1)&z)
            {
                ans+=s[i|x];
                if(!i) break;
            }
        }
        printf("%d\n",ans);
    }

    return 0;
}
Joi是一个流行的Node.js库,用于验证和处理数据。它提供了一种简单而强大的方式来定义和验证数据模式。当处理multipart/form-data时,Joi可以用于验证上传的文件和其他表单字段。 要使用Joi处理multipart/form-data,你需要使用其他中间件(如multer)来解析请求并将文件保存到服务器上。然后,你可以使用Joi来验证文件和其他表单字段的值。 以下是一个使用Joi处理multipart/form-data的示例代码: ```javascript const Joi = require('joi'); const multer = require('multer'); // 创建multer实例并配置文件存储位置 const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'uploads/'); // 文件存储在uploads文件夹中 }, filename: function (req, file, cb) { cb(null, file.originalname); // 使用原始文件名作为文件名 } }); const upload = multer({ storage: storage }); // 定义Joi验证规则 const schema = Joi.object({ username: Joi.string().required(), avatar: Joi.string().required(), file: Joi.string().required() }); // 处理上传请求 app.post('/upload', upload.single('file'), (req, res) => { // 验证请求数据 const { error, value } = schema.validate(req.body); if (error) { // 数据验证失败 res.status(400).json({ error: error.details[0].message }); } else { // 数据验证成功 // 执行其他操作... res.json({ message: '上传成功' }); } }); ``` 在上面的示例中,我们使用multer中间件来解析multipart/form-data请求,并将文件保存到服务器上的uploads文件夹中。然后,我们使用Joi来定义验证规则,并在处理上传请求时验证请求数据。如果验证失败,我们返回一个错误响应;如果验证成功,我们可以执行其他操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值