“我在35度的太阳底下 放 寒 假”部分题解

这是某次校内赛的部分题解,之所以说“部分”,是因为①E题难度太大,其来源是cf中 *2500 的“Rectangle Painting 2”,我做不了,就将其替换成了 *2300 的 “Rectangle Painting 1”;②G题不想写题解了。

A.Tricks Device [HDU - 5294]

给出一张 n 个点,m 条边的无向图,每条边有边长 wi 。求①最少删去几条边使得从 1 号点到 n 号点的最短路数值变化;②最多删几条边使得从 1 号点到 n 号点的最短路数值不变。 n<=2000, m<=60000, 1<=wi<=100

首先说明,网上很多用网络流做,在我学会网络流之前,就先写一个不用网络流的方法吧。

对于②,只需找到从 1 到 n 的最短路中的最少路数就行了。设最少路数为 num,那么答案为 res2=m-num 。

对于①,稍有难度。首先求出 1 到所有点的单源最短路,借此我们可以得到从 1 到 n 的若干条具体的最短路径,将其再建一张图,这是一张从 1 到 n (或从 n 到 1 )的有向图。现在,我们只需求出在新图中最少删几条边就能使 1 和 n 不连通。思考后可知,必须要使整个路径在中间的某些点 u1、u2…处断开,使得从起点只能到达 u1、u2等点,而无法到达终点。而且 u1、u2 等点应无法相互到达(若从 u1 能到达 u2 ,那只删 u1 或只删 u2 不就行了吗)。于是便有了一个利用拓扑序求解的好办法:

首先,建立由最短路径组成的新图:

//我的这个图是从 1 到 n 的有向图
q2.push(n);
while(!q2.empty())
{
    int u=q2.front(); q2.pop();
    for(int p=Edge::h[u]; p; p=e[p].next)
    {
        int v=e[p].to;
        if(d[u]==d[v]+e[p].w)
        {
            if(col[v]==0)
            {
                q2.push(v);
                col[v]=1;
            }
            V[v].push_back(u);
        }
    }
}

然后求入度,为拓扑做准备

for(int i=1; i<=n; ++i)
{
    if(V[i].size())
    {
        int sz=V[i].size();
        for(int j=0; j<sz; ++j)
        {
            du[V[i][j]]++;
        }
    }
}

最后是最关键的拓扑环节,答案保存在 res1 中:

q2.push(1); int cur=0;
while(!q2.empty())
{
    int u=q2.front(); q2.pop();
    int sz=V[u].size();
    cur-=du[u];						//关键步骤,意为“恢复所有到达v点的边”
    for(int j=0; j<sz; ++j)
    {
        int v=V[u][j];
        du[v]--;
        if(du[v]==0) q2.push(v);
        cur++;						//关键步骤,意为“撤掉所有由v出发的边”
    }
    if(u!=n) res1=min(res1,cur);	//关键步骤,更新答案
}

下面奉上完整代码:(话说这么一大坨代码划着好麻烦啊,有没有什么好办法呢?)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;

const int N=2010, M=60010, INF=1e9+7;

class Node
{
public:
    int u, d;
    Node(int u=0, int d=0):u(u),d(d) {};
    bool operator < (const Node & t) const
    {
        return d>t.d;
    }
};

int n, m;
int d[N], du[N], dis[N];
priority_queue<Node> Q;
queue<int> q2;
vector<int> V[N];

class Edge
{
public:
    int to, next, w;
    Edge(int to=0, int next=0, int w=0):to(to),next(next),w(w) {};
    static int h[N], hn;
    static void add(int,int,int);
}e[M<<1];
int Edge::hn, Edge::h[N];
void Edge::add(int u, int v, int w)
{
    e[++Edge::hn]=Edge(v,Edge::h[u],w);
    Edge::h[u]=Edge::hn;
}

void dijkstra(int s)
{
    while(!Q.empty()) Q.pop();
    for(int i=0; i<=n; ++i) d[i]=INF;
    d[s]=0; Q.push(Node(s,0));

    while(!Q.empty())
    {
        Node tmp=Q.top(); Q.pop();
        if(tmp.d!=d[tmp.u]) continue;
        int u=tmp.u;
//        printf("u=%d\n", u);
        for(int p=Edge::h[u]; p; p=e[p].next)
        {
            int v=e[p].to;
            if(d[v]>d[u]+e[p].w)
            {
                d[v]=d[u]+e[p].w;
                Q.push(Node(v,d[v]));
            }
        }
    }
}

int col[N];

void solve()
{
    for(int i=1; i<=n; ++i)
    {
        du[i]=0;
        col[i]=0;
        V[i].clear();
        dis[i]=INF;
        Edge::h[i]=0;
    }
    while(!q2.empty()) q2.pop();
    Edge::hn=0;
    int res1=INF, res2=INF;
    for(int i=1, u, v, w; i<=m; ++i)
    {
        scanf("%d%d%d", &u, &v, &w);
        Edge::add(u,v,w);
        Edge::add(v,u,w);
    }
    dijkstra(1);

    q2.push(n);
    while(!q2.empty())
    {
        int u=q2.front(); q2.pop();
//        printf("u=%d\n", u);
        for(int p=Edge::h[u]; p; p=e[p].next)
        {
            int v=e[p].to;
//            printf("  v=%d\n", v);
            if(d[u]==d[v]+e[p].w)
            {
                if(col[v]==0)
                {
                    q2.push(v);
                    col[v]=1;
                }
                V[v].push_back(u);
            }
        }
    }

    for(int i=1; i<=n; ++i)
    {
//        printf("i=%d\n", i);
        if(V[i].size())
        {
//            printf("u=%d : ", i);
            int sz=V[i].size();
            for(int j=0; j<sz; ++j)
            {
                du[V[i][j]]++;
//                printf(" %d", V[i][j]);
            }
//            puts("");
        }
    }
//    for(int i=1; i<=n; ++i) printf("du[%d]=%d\n", i, du[i]);
    dis[1]=0; q2.push(1); int cur=0;
    while(!q2.empty())
    {
        int u=q2.front(); q2.pop();
        int sz=V[u].size(); cur-=du[u];
        for(int j=0; j<sz; ++j)
        {
            int v=V[u][j];
            du[v]--;
            if(du[v]==0)
            {
                q2.push(v);
            }
            cur++;
            dis[v]=min(dis[v],dis[u]+1);
        }
//        printf("cur=%d\n", cur);
        if(u!=n) res1=min(res1,cur);
    }
    res2=m-dis[n];
    printf("%d %d\n", res1, res2);
}

int main()
{
    while(~scanf("%d%d", &n, &m)) solve();
    return 0;
}

B.搬寝室[HDU - 1421]

给 n 个数,要求从中选出 2*k 个数两两配对,每对数的代价为差的平方,求最小的代价和。(如 5 和 3 配对,代价为 (5-3)2=9 ) 2<=2*k<=n<=2000, 每个数为小于 215 的正整数

排序后,相邻地取数代价最小。考虑dp, f[i][j]表示前 i 个数中取了 j 对数的最小代价和,那么
f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] , f [ i − 2 ] [ j − 1 ] + ( a [ i ] − a [ i − 1 ] ) 2 ) f[i][j]=min(f[i-1][j],f[i-2][j-1]+(a[i]-a[i-1])^2) f[i][j]=min(f[i1][j],f[i2][j1]+(a[i]a[i1])2)
f[i][j] 如果是 2000*2000 的数组的话会MLE,因此我改成了 3*2000。
还有,此题多组数据。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N=2010;
const LL INF=(1ll<<30)*2010;

int n, k;
int a[N];
LL f[3][N];

int main()
{
    while(~scanf("%d%d", &n, &k))
    {
        for(int i=1; i<=n; ++i)
        {
            scanf("%d", a+i);
        }
        sort(a+1,a+n+1);
        for(int i=0; i<3; ++i) for(int j=1; j<=n; ++j) f[i][j]=INF;
        int t=0;
        for(int i=2, o, p, q; i<=n; ++i)
        {
            o=i%3; p=(i-1)%3; q=(i-2)%3;
            for(int j=1; j+j<=i; ++j)
            {
                f[o][j]=min(f[p][j],f[q][j-1]+(a[i]-a[i-1])*1ll*(a[i]-a[i-1]));
            }
        }
        printf("%lld\n", f[n%3][k]);
    }
    return 0;
}

C.Doing Homework again[HDU - 1789]

现有 n 份作业,每份作业需 1 单位时间完成,第 i 份作业截止时间 ti,若截止后才完成,则扣去 ai 分,求最少扣去多少分。 n<=1000

扣分最少即得分最多呗。
可以看到,每份作业若是在截止日期后才做,那大可放在最后做。
首先按 ti 排序,然后按时间顺序做作业,若是当前日期 cur 大于当前作业日期 ti ,那么用这项作业把之前做了的作业中分值最少的那个替换掉(当然,前提是之前分值最少的那个的分值要小于现在这个的分值),这个操作可以用优先队列实现。因此复杂度 nlogn。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

const int N=1010;

class Data
{
public:
    int t, a;
    static bool cmp(Data&x, Data&y)
    {
        return x.t==y.t?x.a>y.a:x.t<y.t;
    }
}d[N];

int n;
priority_queue<int> Q;

void solve()
{
    int sum=0, cnt=0;
    scanf("%d", &n);
    for(int i=1; i<=n; ++i)
    {
        scanf("%d", &d[i].t);
    }
    for(int i=1; i<=n; ++i)
    {
        scanf("%d", &d[i].a);
        sum+=d[i].a;
    }
    sort(d+1,d+n+1,Data::cmp);
    while(!Q.empty()) Q.pop();
    int day=0;
    for(int i=1; i<=n; ++i)
    {
        if(d[i].t>day)
        {
            Q.push(-d[i].a);
            day++;
            cnt+=d[i].a;
        }else if(!Q.empty() && -Q.top()<d[i].a)
        {
            cnt+=Q.top(); Q.pop();
            Q.push(-d[i].a); cnt+=d[i].a;
        }
    }
    printf("%d\n", sum-cnt);
}

int main()
{
    int T; cin>>T;
    while(T--) solve();
    return 0;
}

D.Almost Regular Bracket Sequence[CodeForces - 1095E]

有一个仅包含 ‘(’ 和 ‘)’ 的括号序列s。 如果括号序列的左右括号可以匹配,则是合法的,否则就是不合法的。(比如"()()“和”(())“是合法的;”)(“和”())"是不合法的)。 现在你只能改变某一个位置的括号(左括号变右括号或者右括号变左括号),请问有几个可以选择的位置。 1<=n<=106,n 为 s 长度

*1900 难度

对于这种括号匹配问题,一个套路就是以 ‘(’ 为 1,以 ‘)’ 为 -1,进行前缀和,此题也不例外。
求前缀和 sum 后,若 sum[n]=2或-2,才有可选择的位置。
若 sum[n]=2,则缺少一个 ‘)’ 。就从右到左寻找第一个 sum[i]=1 的位置,再从 i 向右找第一个 sum[j]=2 的位置,那么在 [j,n] 内 ‘(’ 的个数就是答案。
若 sum[n]=-2,对称做即可。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

const int N=1e6+10;
int n;
char s[N], t[N];
int a[N];

int main()
{
    scanf("%d%s", &n, t+1);
    for(int i=1; i<=n; ++i)
    {
        if(t[i]=='(') a[i]=a[i-1]+1;
        else a[i]=a[i-1]-1;
    }
    if(a[n]==2)
    {
        for(int i=1; i<=n; ++i)
        {
            s[i]=t[i];
//            printf("%d ", a[i]);
        }
    }
    if(a[n]==-2)
    {
        for(int i=1; i<=n; ++i)
        {
            if(t[n-i+1]=='(') s[i]=')';
            else s[i]='(';
            if(s[i]=='(') a[i]=a[i-1]+1;
            else a[i]=a[i-1]-1;
//            printf("%d ", a[i]);
        }
    }
    int res=0;
    if(a[n]==2)
    {
        int w=0; bool ok=true;
        for(int i=n; i>=1; --i)
        {
            if(a[i]<0)
            {
                ok=false;
                break;
            }
            if(a[i]==1&&w==0) w=i;
        }
        if(ok)
        {
            for(int i=w+1; i<=n; ++i)
            {
                if(s[i]=='(') res++;
            }
        }
    }
    printf("%d\n", res);
    return 0;
}

E.Rectangle Painting 1[CodeForces - 1098D]

给一个 n*n 的网格,每个格子用 ‘#’ 或 ‘.’ 代表,你每次可以选一个 h*w 的矩形将其全涂成 ‘.’ ,代价是 max(h,w),求将全部 ‘#’ 涂成 ‘.’ 需要的最小代价是多少。 1<=n<=50

*2300难度

易知,若一矩形 H*W (H>W) 的最大代价是H。若想减少代价,必需有某一列无 ‘#’。
考虑dp。 f[x1][x2][y1][y2] 是将 x∈[x1,x2],y∈[y1,y2] 内全变为 ‘.’ 需要的最小代价。
若 x2-x1>=y2-y1,则
f [ x 1 ] [ x 2 ] [ y 1 ] [ y 2 ] = m i n ( x 2 − x 1 + 1 , f [ x 1 ] [ i − 1 ] [ y 1 ] [ y 2 ] + f [ i + 1 ] [ x 2 ] [ y 1 ] [ y 2 ] ) , 第 i 列 无 ′ # ′ f[x1][x2][y1][y2]=min(x2-x1+1,f[x1][i-1][y1][y2]+f[i+1][x2][y1][y2]),第 i 列无 '\#' f[x1][x2][y1][y2]=min(x2x1+1,f[x1][i1][y1][y2]+f[i+1][x2][y1][y2])i#
若 x2-x1<=y2-y1,同理。
至于判断是否全为 ‘.’,预处理前缀和即可。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
 
const int N=50;
 
int n, sig;
char s[N+10];
int a[N+10][N+10], sum[N+10][N+10];
int f[N+2][N+2][N+2][N+2];
 
int cnt(int x, int y, int xx, int yy)
{
    return sum[xx][y]-sum[x-1][y]-sum[xx][yy-1]+sum[x-1][yy-1];
}
 
int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i)
    {
        scanf("%s", s+1);
        for(int j=1; j<=n; ++j)
        {
            if(s[j]=='#') a[i][j]=1;
        }
    }
 
    for(int i=1; i<=n; ++i)
    {
        for(int j=1; j<=n; ++j)
        {
            sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j];
        }
    }
    for(int x1=1; x1<=n; ++x1)
    {
        for(int x2=x1; x2>=1; --x2)
        {
            for(int y1=1; y1<=n; ++y1)
            {
                for(int y2=y1; y2>=1; --y2)
                {
                    f[x1][x2][y1][y2]=max(x1-x2,y1-y2)+1;
                    if(x1-x2>=y1-y2)
                    {
                        for(int i=x2; i<=x1; ++i)
                        {
                            if(cnt(i,y1,i,y2)==0)
                            {
                                f[x1][x2][y1][y2]=min(f[x1][x2][y1][y2],f[i-1][x2][y1][y2]+f[x1][i+1][y1][y2]);
                            }
                        }
                    }
                    if(x1-x2<=y1-y2)
                    {
                        for(int j=y2; j<=y1; ++j)
                        {
                            if(cnt(x2,j,x1,j)==0)
                            {
                                f[x1][x2][y1][y2]=min(f[x1][x2][y1][y2],f[x1][x2][j-1][y2]+f[x1][x2][y1][j+1]);
                            }
                        }
                    }
                }
            }
        }
    }
    printf("%d\n", f[n][1][n][1]);
    return 0;
}

F.Maximum Value[CodeForces - 485D]

给出一个数列 a 由 n 个整数组成,求 ai%aj 的最大值(ai>aj)。 1<=n<=2*105, 1<=ai<=106

*2100难度

若 % ai 的最大值,只需对于所有 x=ai*k ,(k>=2,k*ai<=2*106),对每次数列中小于 x 的最大值 aj 求 aj%ai 即可。
预处理后,aj 可以 O(1) 得到。由于 1+1/2+1/3+1/4+…+1/n≈log2(n+1)+C,此题复杂度为 O(MlogM),M=2*106,也可以取 M 为 a 中的最大值。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
 
const int N=1e6;
 
int n;
int a[N*2+10], pre[N*2+10];
 
int main()
{
    scanf("%d", &n);
    for(int i=1, x; i<=n; ++i)
    {
        scanf("%d", &x);
        a[x]=1;
    }
    for(int i=2; i<=N*2; ++i)
    {
        if(a[i-1]) pre[i]=i-1;
        else pre[i]=pre[i-1];
    }
    int res=0;
    for(int i=2; i<=N; ++i)
    {
        if(a[i]==0) continue;
        for(int j=i+i; j<=N*2; j+=i)
        {
            res=max(res,pre[j]%i);
        }
    }
    printf("%d\n", res);
    return 0;
}

G.Mister B and PR Shifts[CodeForces - 820D]

*1900难度

前面说过不想写题解了。
就放一个别人的题解吧。
https://www.cnblogs.com/TnT2333333/p/7091392.html

.
.
.

感谢客官看到这里。有什么问题欢迎指出!_(:3」∠❀)_

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值