2022“杭电杯”中国大学生算法设计超级联赛(8)

  1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
赛时过题 O     O     O O     O    
赛后补题   O                      

赛后总结:

杭电的机子实在是太太太太太太太太太卡常了!!!!!!

1002我的线段树只需要在push_up和push_down的时候加上inline就过了。。。

好吧,这次遇到了,下次一定。

卡常技巧:inline,尽可能少使用vector,循环展开,if(>=mod)-=mod和if(<0)+=mod代替%mod,不会被修改的传参用const&。

有一说一,在发现被卡常的时候应该一边让队友疯狂交,一边尽可能修改代码减小常数。。。而不是光疯狂交不改代码。。。

主要还是因为有一发卡进去了让我有了侥幸心理,ε=(´ο`*)))唉

越想越可惜,只差这一题就可以校队第二了,害

今天这场也让我深刻感受到了,要注意自己的长处和短处,比如我的长处可能是dp和数据结构,短处可能是贪心,一般贪心的题都有很多人过但事实上我不一定能立刻做出来,像今天这个1002赛时只有70个人做出来了,但我也能做出来,这就是我的长处。

赛时排名:

6题末尾:146名

7题末尾:77名

8题末尾:55名


1004 Quel'Thalas

题目难度:check-in

题目大意:有一个正方形,从(0,0)~(n,n)总共有(n+1)2个点。每次可以选择一条直线覆盖该直线上的所有点。

问把除了(0,0)的(n+1)2-1个点全部覆盖,最少要几条直线

解题分析:随便画一下,如果笔直的切,数量是2*n,如果是斜向右下切,数量也是2*n,那么大胆猜测答案是2*n即可。

参考代码:

查看代码
/*#if(__cplusplus == 201103L)
#include <unordered_map>
#include <unordered_set>
#else
#include <tr1/unordered_map>
#include <tr1/unordered_set>
namespace std
{
    using std::tr1::unordered_map;
    using std::tr1::unordered_set;
}
#endif*/
#include<bits/stdc++.h>
using namespace std;
int n,T;
int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n;
		cout<<n*2<<endl;
	}
	return 0;
 }

1001 Theramore

题目难度:easy

题目大意:给定一个字符串,每次操作可以选择长度为奇数的区间进行前后翻转,询问能得到的最小字典序的字符串是什么。

赛时经历:

随便模拟了一下,为了让字典序最小,那么当前位肯定要尽可能是0,发现很难贪心,要么尽可能把1往后放,要么尽可能把0往前提,还能发现长度为偶数的区间可以任意移动。

再看一下样例,发现样例的答案都是0000 101010 1111

然后稍微证一下,发现最优的情况一定是0000 101010 1111,没办法更优了

那么考虑维护101010 11111

假设当前已经处理到第i位,前i-1位字符串已经到达最优的0000 101010 1111 形式,那么考虑第i位:

①第i位是'0'

要尽可能把这个'0'往前提,如果101010 1111的长度为偶数,那么直接把这个区间和当前这个0放在一起翻转,形成0 1111 01010101,由于010101长度一定是偶数,偶数区间可以任意移动,那么一定可以形成0 01010101 1111,把前两个0输出出来然后再维护后面的101010 11111即可。

②第i位是'1'

要尽可能把这个'1'往后方,那么发现直接把它放最后是最优的。

标答分析:

发现翻一个长度为奇数(设为k)的区间,能再翻其内部长度为k-2的区间,那么就相当于交换前后两个数。

还能发现任意一种翻区间的方式,都能通过交换前后2个数构造出来。

换而言之,该问题等价于:将奇数位置和偶数位置分别考虑,字典序最小是多少?

那么对奇数位置和偶数位置分别排序即可。

(难怪考场这么多人过,是我想复杂了)

参考代码:

查看代码
 #include<iostream>
#include<cstdio>
#include<cstring>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+1000;
char s[N];
int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		scanf("%s",s+1);
		int len=strlen(s+1);
		int cnt10=0,cnt1=0;
		For(i,1,len) 
		{
			if (s[i]=='0')
			{
				if (!(cnt1&1)) 
				{
					putchar('0');
					if (cnt10) 
					{
						putchar('0');
						cnt10--;
						cnt1++;
					}
				}
				else cnt1--,cnt10++;
			}
			else cnt1++;
//			printf("!! %d %d %d\n",i,cnt10,cnt1);
		}
		For(i,1,cnt10) putchar('1'),putchar('0');
		For(i,1,cnt1) putchar('1');putchar('\n');
	}
	return 0;
} 

1011 Stormwind

题目难度:check-in

题目大意:一个n*m的长方形,可以沿水平或竖直方向画若干条线,每条线的两端点都在长方形的边界上。要求这些线划分出的每个小长方形面积都≥k,求最多可以画几条线。

数据范围:T≤100,n,m,k≤1e5

解题分析:

首先先假设已经画好了所有竖线,那么可以贪心画横线。为了让横线尽可能多,那么每个长方形都要尽可能逼近k,可以发现这个仅和画好竖线后的最短的那一段有关,根据最短的那一段作为宽,可以确定横线最短为多少。

那么就只要枚举画好竖线的最短的那一段的长度,那么竖线可以贪心画,同时横线也可以贪心画。

赛时经历:

这题我赛时没开,是宇彬做的,赛后补题发现是一道签到题,不知道为啥这题赛时会WA两发(

参考代码:

查看代码
 #include<iostream>
#include<cstdio>
#define For(i,a,b) for(int i=a;i<=b;i++)
int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		long long n,m,k;scanf("%lld%lld%lld",&n,&m,&k);
		long long ans=0;
		For(i,1,m)
		{
			int j=(k+i-1)/i;if (j>n) continue;
			ans=std::max(ans,m/i-1+n/j-1);
//			printf("?? %d %lld %d %lld\n",i,m/i-1,j,n/j-1);
		}
		printf("%lld\n",ans);
	}
	return 0;
 } 

1008 Orgrimmar

题目难度:easy

题目大意:求一棵树的最大分离集的大小。最大分离集是一个点集,点集中的每个点最多和一个点联通,即若干孤立点+若干点对。

赛时情况:

一眼树形DP。每个点设三个状态不选、选了但孤立,选了且以匹配为点对,直接DP即可。

纯纯签到题(,不过在代码里修改栈空间还是第一次见

参考代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<cstdlib>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=5e5+1000;
const int INF=0x3f3f3f3f;
int dp[N][5];
std::vector<int> To[N];
void dfs(int now,int pa)
{
	dp[now][0]=0;
	dp[now][1]=1;
	int sum=0;
	For(i,0,(int)To[now].size()-1) 
	{
		int v=To[now][i];if (v==pa) continue;
		dfs(v,now);
		dp[now][0]+=std::max(std::max(dp[v][0],dp[v][1]),dp[v][2]);
		dp[now][1]+=dp[v][0];
		sum+=dp[v][0];
	}
	dp[now][2]=-INF;
	For(i,0,(int)To[now].size()-1) 
	{
		int v=To[now][i];if (v==pa) continue;
		dp[now][2]=std::max(dp[now][2],sum-dp[v][0]+dp[v][1]+1);
	}
}
void Solve()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		int n;scanf("%d",&n);
		For(i,1,n) To[i].clear();
		For(i,1,n-1)
		{
			int u,v;scanf("%d%d",&u,&v);
			To[u].push_back(v);To[v].push_back(u);
		}
		dfs(1,0);printf("%d\n",std::max(std::max(dp[1][0],dp[1][1]),dp[1][2]));
	}
	return ;
}
int main() {
    int size(512<<20);  // 512M
    __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
    Solve();
    exit(0);
}

1002 Darkmoon Faire

题目难度:medium-hard

题目大意:一个数列,保证所有数互不相同。现在要把数列划分成若干段,每一段视为 1-base 的数组,要求段内最大值在奇数下标,段内最小值在偶数下标。求划分的方案数。

赛时经历:

先考虑暴力怎么做?一眼DP,令dp[i]代表以i结尾的划分方案数,那么dp[i]就枚举最后一段的长度同时求最大最小值,从dp[n-1]~dp[1]转移,答案就是dp[n],复杂度O(n^2)

考虑加速DP的过程。

首先枚举最后一段的最大最小值可以通过单调栈来维护。

然后发现最后一个区间就两种情况:起始下标、最大值下标、最小值下标为:偶偶奇/奇奇偶。

对这两个数列分别需要维护对每个起始位置x,从x开始的最大最小值的下标的奇偶性,如果最大最小值下标奇偶性合法,那么就要将其计入dp[i],考虑用线段树维护。

不妨把所有起始位置按照奇偶性分开来考虑,那么对于奇数起始位置最大最小值下标为“奇偶”,对于偶数起始位置最大最小值下标为“偶奇”。

线段树要如何维护最大最小值下标的奇偶性呢?考虑一个数一个数的加入序列,对于新加入的数,以它作为最大/最小值的起始位置x一定是一段区间,对这两个区间分别进行操作即可。

接下来分情况考虑:

令sum[root][0/1][0/1][0/1]表示当前线段树区间root,起始下标为偶/奇,最小值下标任意/合法,最大值下标任意/合法 时的区间内所有dp值之和。

起始下标为偶数,新加入的数作为最小值:这段区间的最小值下标奇偶性变成了当前下标i的奇偶性,如果i为奇数那么该段区间每一个点是否计入就只和最大值下标有关,即最小值下标合法从最小值下标任意转移;i为偶数那么该段区间每个点都不计入,即最小值合法的sum置为0。

起始下标为偶数,新加入的数作为最大值:同上

起始下标为奇数,新加入的数作为最小值:同上

起始下标为奇数,新加入的数作为最大值:同上

另外需要注意每次求解dp[i]之前要把dp[i-1]加入sum[root][0/1][0][0]

好不容易花了两个半小时写完了,16:30一交TLE,然后花了十分钟把Push_down和Push_up的传参新加入一个odd,可以让常数除二,结果16:40一交变成了WA。。。

仔细检查+对拍发现是初始化有问题,两个单调栈的top应该分别是top[0]和top[1],我写成了top[1]和top[2],16:56再交又变成了TLE。。。

此时我就慌了,然后我就继续卡常,比如把push_down和push_up内的循环展开,比如把%mod改成+-mod,比如加了快读,比如传参改成const&,一交还是TLE。。。

那我一时是真想不到什么辙了。。。就一直交一直交,希望能卡进4s。。。结果一直不行。

直到赛后半小时不断调试我才发现,关键优化是在push_down和push_up前加inline,这个优化再结合16:40的那个让push_down和push_up新加传参odd,两个优化就过了。。。

我特么裂开,什么老年机啊(恼)!!!

这次这题没做出来有两个主要原因:

①太急了,没想到inline优化,没想到杭电评测机没有默认inline优化

②写太慢了,应该把思路理清楚再开始写,尤其是线段树的状态怎么设置,同时每写完一个子函数都要认真检查保证这个子函数没错。

这次吸取教训了,下次再战!

参考代码:

include<iostream>
#include<cstdio>
#define For(i,a,b) for(int i=a;i<=b;i++)
const long long mod=998244353;
const int N=3e5+1000;
int stack[2][N],top[2];//上升,下降 
int a[N];
long long dp[N]; 
long long sum[4*N][2][2][2];//开始位置为偶/奇,min假设/实际 max假设/实际、
long long tag[4*N][2][2];//开始位置为 偶/奇,min/max的奇偶性改变为tag 
inline void Push_up(int root,int odd)
{
    For(j,0,1) For(k,0,1) 
    {
        sum[root][odd][j][k]=(sum[root<<1][odd][j][k]+sum[root<<1|1][odd][j][k]);
        if (sum[root][odd][j][k]>mod) sum[root][odd][j][k]-=mod;
    }
}
inline void pushdown(int root,int l,int r,int odd)
{
    For(opt,0,1)
    {
        if (tag[root][odd][opt]==-1) continue;
        int v=tag[root][odd][opt];
//        printf("?? %d %d %d %d %d %d\n",root,l,r,odd,opt,v);
        if (opt==0) //min 
        {
            if (v!=odd) 
            {
                sum[root][odd][1][1]=sum[root][odd][0][1];
                sum[root][odd][1][0]=sum[root][odd][0][0];
            }
            else 
            {
                sum[root][odd][1][1]=0;
                sum[root][odd][1][0]=0;
            }
        }
        else//max 
        {
            if (v==odd) 
            {
                sum[root][odd][1][1]=sum[root][odd][1][0];
                sum[root][odd][0][1]=sum[root][odd][0][0];
            }
            else
            { 
                sum[root][odd][1][1]=0;
                sum[root][odd][0][1]=0;
            }
        }
        if (l!=r) tag[root<<1][odd][opt]=tag[root<<1|1][odd][opt]=tag[root][odd][opt];
        tag[root][odd][opt]=-1;
    }
}
inline void Push_down(int root,int l,int r,int odd)
{
    if (l==r) return ;
    int mid=(l+r)>>1;
    pushdown(root<<1,l,mid,odd);
    pushdown(root<<1|1,mid+1,r,odd);
}
void Build(int root,int l,int r)
{
    if (l==r)
    {
        tag[root][0][0]=tag[root][0][1]=tag[root][1][0]=tag[root][1][1]=-1;
        For(i,0,1) For(j,0,1) For(k,0,1) sum[root][i][j][k]=0;
        return ;
    } 
    int mid=(l+r)>>1;
    Build(root<<1,l,mid);Build(root<<1|1,mid+1,r);
    tag[root][0][0]=tag[root][0][1]=tag[root][1][0]=tag[root][1][1]=-1;
    For(i,0,1) For(j,0,1) For(k,0,1) sum[root][i][j][k]=0;
}
void update(int root,int l,int r,int a,int b,int odd,int opt,int v)
{
    Push_down(root,l,r,odd);
    if (l==a&&r==b)
    {
        tag[root][odd][opt]=v;
        pushdown(root,l,r,odd);
        return ;
    }
    int mid=(l+r)>>1;
    if (b<=mid) update(root<<1,l,mid,a,b,odd,opt,v);
    else
    {
        if (a>mid) update(root<<1|1,mid+1,r,a,b,odd,opt,v);
        else update(root<<1,l,mid,a,mid,odd,opt,v),update(root<<1|1,mid+1,r,mid+1,b,odd,opt,v); 
    }
    Push_up(root,odd);
}
void insert(int root,int l,int r,int x,int odd)
{
    Push_down(root,l,r,odd);
    if (l==r)
    {
        (sum[root][odd][0][0]+=dp[2*x+odd-1])%=mod;
        return ;
    }
    int mid=(l+r)>>1;
    if (x<=mid) insert(root<<1,l,mid,x,odd);
    else insert(root<<1|1,mid+1,r,x,odd);
    sum[root][odd][0][0]=(sum[root<<1][odd][0][0]+sum[root<<1|1][odd][0][0])%mod;
    return ;
}
void read(int &x)
{
    x=0;int flag=1;
    char c=getchar();
    while (c<'0'||c>'9')
    {
        if (c=='-') flag=-1;
        c=getchar();
    }
    while ('0'<=c&&c<='9')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    x*=flag;
}
int main()
{
    int T;read(T);
    while (T--)
    {
        int n;read(n);For(i,1,n+1) dp[i]=0;dp[0]=1;
        Build(1,0,n>>1);
        top[1]=top[0]=0;
        For(i,1,n) 
        {
            read(a[i]);
//            a[i]=i&1?i:-i;
            insert(1,0,n>>1,i>>1,i&1);
            while (top[0]>0&&a[stack[0][top[0]]]>=a[i]) top[0]--;stack[0][++top[0]]=i;
            while (top[1]>0&&a[stack[1][top[1]]]<=a[i]) top[1]--;stack[1][++top[1]]=i;
            int L,R;
            L=stack[0][top[0]-1]+1;R=i;
            if (!(L&1)) L++;if (!(R&1)) R--;
            if (L<=R) update(1,0,n>>1,L>>1,R>>1,1,0,i&1);//min=a[i]: L= stack[0][top[0]-1]+1 ~ i  开始位置为奇数 
//            printf("?? %d %d\n",L,R);
            L=stack[1][top[1]-1]+1;R=i;
            if (!(L&1)) L++;if (!(R&1)) R--;
            if (L<=R) update(1,0,n>>1,L>>1,R>>1,1,1,i&1);//max=a[i]: L= stack[1][top[1]-1]+1 ~ i  开始位置为奇数
//            printf("?? %d %d\n",L,R);
            L=stack[0][top[0]-1]+1;R=i;
            if (L&1) L++;if (R&1) R--;
            if (L<=R) update(1,0,n>>1,L>>1,R>>1,0,0,i&1);//min=a[i]: L= stack[0][top[0]-1]+1 ~ i  开始位置为偶数
//            printf("?? %d %d\n",L,R);
            L=stack[1][top[1]-1]+1;R=i;
            if (L&1) L++;if (R&1) R--;
            if (L<=R) update(1,0,n>>1,L>>1,R>>1,0,1,i&1);//max=a[i]: L= stack[1][top[1]-1]+1 ~ i  开始位置为偶数
//            printf("?? %d %d\n",L,R);
            dp[i]=(sum[1][0][1][1]+sum[1][1][1][1])%mod;
//            printf("!! %d %lld\n",i,dp[i]);
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}

1007 Darnassus

题目难度:medium

题目大意:给出一个排列p ,把每个位置视为点,建一个无向图,i,j 之间的边权为|i-j|*|pi-pj|。求这个图的最小生成树。

数据范围:1≤n≤5e5

赛时情况:这题是宇彬做的,一开始随机数据能过,但一交就TLE,怀疑被卡,加了玄学优化就过了。

解题分析:

随便构造一个方案,比如1-2-3-...-n的这样一条链,它的每一条边都=|1|*|pi-pj|=|pi-pj|≤n-1

换句话说,边权>n-1的边全都可以扔了

也就是说只留下边权≤n-1的,边权≤n-1说明|i-j|≤sqrt(n-1)或|pi-pj|≤sqrt(n-1) (如果都>则边权>n-1)

那么就枚举|i-j|≤sqrt(n-1)或|pi-pj|≤sqrt(n-1) 即可,总共有2nsqrt(n)条边

然后再找到所有边以后如果直接sort会超时。。。必须得用桶排

然后如果用普通的桶排需要两倍空间,用传统的int u,v,w会MLE,需要把u和v压到一个unsigned int内。(50000*50000会爆int)(std是采用数组模拟链表来做桶排的)

即使这样还是会超时,需要在kruscal的时候一旦找到了所有n-1条边就直接break

三重优化都加上,2.375s/3s,可算过了。。。

参考代码:

查看代码
 #include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Frd(i,a,b) for(int i=a;i>=b;i--)
#define ABS(x) ((x)<0?-(x):(x))
const int N=5e4+10;
const int M=2*N*300;
struct Edge
{
    unsigned int uv,w;//long long w; 
} e[M],temp[M];int e_cnt,cnt[N];
int pa[N],son[N];
int find(int x){return pa[x]==-1?x:pa[x]=find(pa[x]);}
int p[N],pos[N];
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        int n,sqrtn;
        scanf("%d",&n);sqrtn=sqrt(n-1);
        For(i,1,n) 
        {
            scanf("%d",&p[i]);
            pos[p[i]]=i;
        }
        e_cnt=0;For(i,1,n-1) cnt[i]=0;
        For(i,1,n) //|i-j|*|pi-pj|<=n-1 |i-j|<=sqrt(n-1) or |pi-pj|<=sqrt(n-1)
        {
            For(j,i+1,n) 
            {
                if (j-i>sqrtn) break;
                unsigned int w; 
                w=(unsigned int)(j-i)*ABS(p[j]-p[i]);
                if (w<=n-1) temp[++e_cnt]=(Edge){(unsigned int)i*n+j-1,w},cnt[w]++;
                w=(unsigned int)(j-i)*ABS(pos[j]-pos[i]);
                if (w<=n-1) temp[++e_cnt]=(Edge){(unsigned int)pos[i]*n+pos[j]-1,w},cnt[w]++;
            }
        }
        For(i,1,n-1) cnt[i]+=cnt[i-1];
        For(i,1,e_cnt) e[cnt[temp[i].w]--]=temp[i];
        long long ans=0,now=0;
        For(i,1,n) pa[i]=-1,son[i]=1;
        For(i,1,e_cnt) 
        {
            int u=e[i].uv/n,v=e[i].uv%n+1;
            if (find(u)==find(v)) continue;
            if (son[find(u)]<son[find(v)])
                pa[find(u)]=find(v),son[find(v)]+=son[find(u)];
            else 
                pa[find(v)]=find(u),son[find(u)]+=son[find(v)];
            ans+=e[i].w;
			now++;if (now==n-1) break;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

posted @ 2022-08-11 23:36  th-is  阅读(125)  评论(0)    收藏  举报