2022“杭电杯”中国大学生算法设计超级联赛(5)部分题题解

Buy Figurines

草,一道简单的模拟题,昨天搞了近两个消失都没弄出来,提醒我们,打代码之前先理清思路...
怎么说,我们完全可以按照题意进行模拟,无外乎就是来人,队头走人,维护每个队的人数,我们发现来人,和队头走人的总个数都是O(n)的,那我们呢开一个set,表示整个时间流程,将他们的时间都插到set里面,然后依次取出来模拟。之后还需要注意的是,维护队伍的时间,我们用cnt[i]表示当前队伍i第一个人还没开始做,已经过去的时间,若一个队伍没人的话,把cnt[i]更新成新来的人来的时间即可。怎么说,思路清晰后,打代码还是挺快的。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int T,n,m,t[N],p[N],rs[N];
ll cnt[N];
queue<int>q[N];
set<pair<ll,int> >s;
set<pair<int,int> >qs;
int main()
{
//	freopen("1.in","r",stdin);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		qs.clear();
		for(int i=1;i<=n;++i) 
		{
			scanf("%d%d",&t[i],&p[i]);
			s.insert({t[i],m+i});	
		}
		for(int i=1;i<=m;++i) 
		{
			cnt[i]=0;
			rs[i]=0;
			qs.insert({rs[i],i});
			while(q[i].size()) q[i].pop();
		}
		while(s.size())
		{
			int x=(*s.begin()).second;
			s.erase(s.begin());
			if(x>m)//如果是新来人, 
			{
				x-=m;
				int y=(*qs.begin()).second;
				qs.erase(qs.begin());
				q[y].push(p[x]);
				rs[y]++;
				if(rs[y]==1) 
				{
					cnt[y]=t[x];
					s.insert({t[x]+p[x],y});
				}
				qs.insert({rs[y],y});
			}
			else
			{
				cnt[x]+=q[x].front();
				q[x].pop();
				qs.erase({rs[x],x});
				rs[x]--;
				qs.insert({rs[x],x});
				if(q[x].size()) s.insert({cnt[x]+q[x].front(),x});
			}
		}
		ll ans=0;
		for(int i=1;i<=m;++i) ans=max(ans,cnt[i]);
		printf("%lld\n",ans);
	}
	return 0;
} 

Count Set

题目大意:给定一个序列,要求选出一些元素,形成两个集合,一个集合是所有的元素对应下标的集合,一个集合是所有元素对应值的集合,要求两个集合不能有交集。问不同的方案数?
首先这个题的突破口要从下标和其值对应关系上下手,打过cf大概都知道找下标和其值的关系的时候都会形成一个环。那我们就在这个环上解决问题,发现元素和其下标正好相邻,所以我们只需要选出不相邻的若干个即可。
那么问题就转换成了在长度为n的环上,选出不相邻的k个的方案数。
考虑不相邻的话可以用插空法,我们现在一个序列上考虑这个问题,长度为n的序列,考虑选k个两两不相邻的方案数。我们假定n-k个已经有了,这里是由n-k+1个空的,剩余k个数字可以插进去,所以总的方案数是\((n-k+1)*(n-k)*...*(n-2*k+2)\)同时还发现这k个还会重复,所以最后的方案数就是\(C(n-k+1,k)\).如果要拓展到环形的话,考虑1号点取与不取,取得话,只需要从n-3个球中取出k-1个即可,方案数为\(C(n-k-1,k-1)\).不取的话,从n-1个点中共取k个,则为\(C(n-k,k)\).两个相加即可。求得每个环上取k个的方案数之后,我们还需要把若干个环合到一起统计答案。发现环的方案数的合并就是一个NTT的过程。若干个多项式卷积?
这里有两种方法,启发式合并(事实证明启发式合并大的集合和小的集合都扫的话不影响复杂度。),分治NTT。
启发式合并常数略大:

启发式合并
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e6+10,P=998244353,G=3,Gi=332748118;
int T,n,k,p[N],vis[N];
int bit,tot,rev[N],cnt[N],a[N],b[N];
int jc[N],jc_inv[N];
vector<int>v[N];
priority_queue<pair<int,int> >q;
inline int power(int x,int y)
{
    int ans=1;
    while(y)
    {
        if(y&1) ans=(ll)ans*x%P;
        y>>=1;
        x=(ll)x*x%P;
    }
    return ans%P;
}
inline void NTT(int *a,int inv)
{
    for(int i=0;i<tot;++i)
        if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int mid=1;mid<tot;mid<<=1)
    {
        int w1=power(inv==1?G:Gi,(P-1)/(mid<<1));
        for(int i=0;i<tot;i+=mid*2)
        {
            int wk=1;
            for(int j=0;j<mid;++j,wk=(ll)wk*w1%P)
            {
                int x=a[i+j],y=(ll)wk*a[i+j+mid]%P;
                a[i+j]=((ll)x+y)%P;a[i+j+mid]=((ll)x-y+P)%P;
            }
        }
    }
}
inline void prework()
{
    jc[0]=1;jc_inv[0]=1;
    for(int i=1;i<=5e5+10;++i) jc[i]=(ll)jc[i-1]*i%P;
    for(int i=1;i<=5e5+10;++i) jc_inv[i]=power(jc[i],P-2);
}
inline int C(int n,int m) {return (ll)jc[n]*jc_inv[n-m]%P*jc_inv[m]%P;}
inline int get(int len,int k)
{
    if(len<2*k) return 0;
    if(k==0)return 1;
    return (ll)C(len-k,k)*len%P*power(len-k,P-2)%P;
}
inline void merge(int tx,int ty)
{
    bit=0;
    while((1<<bit)<cnt[tx]+cnt[ty]+1) bit++;
    tot=1<<bit;
    for(int i=0;i<tot;++i) a[i]=0;
    for(int i=0;i<tot;++i) b[i]=0;
    for(int i=0;i<=cnt[tx];++i) a[i]=v[tx][i];
    for(int i=0;i<=cnt[ty];++i) b[i]=v[ty][i];
    for(int i=0;i<tot;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    NTT(a,1);NTT(b,1);
    for(int i=0;i<tot;++i) a[i]=(ll)a[i]*b[i]%P;
    NTT(a,-1);
    int inv=power(tot,P-2);
    for(int i=0;i<=cnt[tx]+cnt[ty];++i) a[i]=(ll)a[i]*inv%P;
    while(v[tx].size()!=cnt[tx]+cnt[ty]+1) v[tx].push_back(0);
    for(int i=0;i<=cnt[tx]+cnt[ty];++i) v[tx][i]=a[i];
    cnt[tx]+=cnt[ty];
}
int main()
{
    //freopen("1.in","r",stdin);
    prework();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i) 
        {
            scanf("%d",&p[i]);
            vis[i]=0;    
        }
        if(2*k>n) 
        {
            puts("0");
            continue;
        }
        int num=0,sum=0;
        for(int i=1;i<=n;++i)
        {
            if(vis[i]) continue;
            int now=i,bgs=1;
            vis[now]=1;
            while(p[now]!=i)
            {
                bgs++;
                now=p[now];
                vis[now]=1;
            }
            if(bgs==1) continue;
            ++num;v[num].clear();
            cnt[num]=bgs/2;
            for(int j=0;j<=cnt[num];++j) v[num].push_back(get(bgs,j));
            q.push(make_pair(-cnt[num],num));
            sum+=cnt[num];
        }
        if(sum<k){puts("0");continue;}
        while(q.size()>1)
        {
            int t1=q.top().second;q.pop();//最小的 
            int t2=q.top().second;q.pop();//次小的 
            merge(t2,t1);//最小的向次小的合并. 
            q.push(make_pair(-cnt[t2],t2));
        }
        printf("%d\n",v[q.top().second][k]);
        q.pop();
    }
    return 0;
} 
分治的话就是将多项式按个数分治
分治NTT
//#include<bitsdc++.h>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cctype>
#include<queue>
#include<deque>
#include<stack>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<cctype>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<vector>
#include<algorithm>
#include<utility>
#include<bitset>
#include<set>
#include<map>
#define ll long long
using namespace std;
const int N=3e6+10,P=998244353,G=3,Gi=332748118;
int T,n,k,p[N],vis[N];
int bit,tot,rev[N],cnt[N],a[N],b[N];
int jc[N],jc_inv[N];
vector<int>v[N];
inline int power(int x,int y)
{
    int ans=1;
    while(y)
    {
        if(y&1) ans=(ll)ans*x%P;
        y>>=1;
        x=(ll)x*x%P;
    }
    return ans%P;
}
inline void NTT(int *a,int inv)
{
    for(int i=0;i<tot;++i)
        if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int mid=1;mid<tot;mid<<=1)
    {
        int w1=power(inv==1?G:Gi,(P-1)/(mid<<1));
        for(int i=0;i<tot;i+=mid*2)
        {
            int wk=1;
            for(int j=0;j<mid;++j,wk=(ll)wk*w1%P)
            {
                int x=a[i+j],y=(ll)wk*a[i+j+mid]%P;
                a[i+j]=((ll)x+y)%P;a[i+j+mid]=((ll)x-y+P)%P;
            }
        }
    }
}
inline void prework()
{
    jc[0]=1;jc_inv[0]=1;
    for(int i=1;i<=5e5+10;++i) jc[i]=(ll)jc[i-1]*i%P;
    for(int i=1;i<=5e5+10;++i) jc_inv[i]=power(jc[i],P-2);
}
inline int C(int n,int m) {return (ll)jc[n]*jc_inv[n-m]%P*jc_inv[m]%P;}
inline int get(int len,int k)
{
    if(len<2*k) return 0;
	if(k==0)return 1;
    return (ll)C(len-k,k)*len%P*power(len-k,P-2)%P;
}
inline void merge(int tx,int ty)
{
    bit=0;
    while((1<<bit)<cnt[tx]+cnt[ty]+1) bit++;
    tot=1<<bit;
    for(int i=0;i<tot;++i) a[i]=0;
    for(int i=0;i<tot;++i) b[i]=0;
    for(int i=0;i<=cnt[tx];++i) a[i]=v[tx][i];
    for(int i=0;i<=cnt[ty];++i) b[i]=v[ty][i];
    for(int i=0;i<tot;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    NTT(a,1);NTT(b,1);
    for(int i=0;i<tot;++i) a[i]=(ll)a[i]*b[i]%P;
    NTT(a,-1);
    int inv=power(tot,P-2);
    for(int i=0;i<=cnt[tx]+cnt[ty];++i) a[i]=(ll)a[i]*inv%P;
    while(v[tx].size()!=cnt[tx]+cnt[ty]+1) v[tx].push_back(0);
    for(int i=0;i<=cnt[tx]+cnt[ty];++i) v[tx][i]=a[i];
    cnt[tx]+=cnt[ty];
}
inline void CDQ(int l,int r)
{
	if(l==r)return;
	int mid=(l+r)>>1;
	CDQ(l,mid);
	CDQ(mid+1,r);
	merge(r,mid);
	return;
}
int main()
{
    freopen("1.in","r",stdin);
    prework();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i) 
        {
            scanf("%d",&p[i]);
            vis[i]=0;    
        }
        if(2*k>n) 
        {
            puts("0");
            continue;
        }
        int num=0,sum=0;
        for(int i=1;i<=n;++i)
        {
            if(vis[i]) continue;
            int now=i,bgs=1;
            vis[now]=1;
            while(p[now]!=i)
            {
                bgs++;
                now=p[now];
                vis[now]=1;
            }
            if(bgs==1) continue;
            ++num;v[num].clear();
			cnt[num]=bgs/2;
            for(int j=0;j<=cnt[num];++j) v[num].push_back(get(bgs,j));
			sum+=cnt[num];
        }
		if(sum<k)
		{
			puts("0");continue;
		}
		CDQ(1,num);
		printf("%d\n",v[num][k]);
    }
    return 0;
}

The Surveying

这个题也是计算几何题,这种题其实也就套着几何的外衣,和其他算法结合的题目。不要怂,淦!
看到n=20,其实很容易想到状压,状压之后,我们还需要知道所有的关键点的覆盖情况,发现这个可以用bitset去维护(复杂度除以64,美滋滋!)。状压的过程中,我们需要知道一个监测点能够覆盖的关键点的情况,这个需要预处理出来,还有一个监测点状态s,是否合法,(一个监测点至少能让另外一个监测点看到。)所以总的复杂度是\(O(2^{20}*20*400/64)\)
当只判断符号的时候,还是用dcmp比较好...别问我是怎么知道的,...,(爆longlong血的教训)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=23,M=410,maxn=(1<<21),INF=1e9;
int T,n,m,cnt[maxn];
bool vis[maxn],isgo[N][N];
bitset<M>g[N],f;
struct Point
{
    ll x,y;
    Point(ll _x=0,ll _y=0):x(_x),y(_y){}
    bool friend operator ==(Point a,Point b)
    {
        if(a.x==b.x&&a.y==b.y) return true;
        return false;
    }
}p[N],a[M][4];
typedef Point Vector;
inline int dcmp(ll x)
{
    if(x==0) return 0;
    return x>0?1:-1;
}
inline ll Dot(Vector a,Vector b){return a.x*b.x+a.y*b.y;}
inline ll Cross(Vector a,Vector b){return dcmp(a.x*b.y-b.x*a.y);}
Vector operator -(Vector a,Vector b){return Vector(a.x-b.x,a.y-b.y);} 
inline bool getin(Point Pi,Point Pj,Point Qi,Point Qj)
{
    return (Cross(Pj-Pi,Qi-Pi)*Cross(Pj-Pi,Qj-Pi)<=0);
}
inline bool check(Point x,Point y)
{
    for(int i=1;i<=m;++i)
    {
        for(int j=0;j<=3;++j)
        {
            Point P=a[i][j],Q=a[i][(j+1)%4];
            if(y==P||y==Q) continue;
            if(getin(x,y,P,Q)&&getin(P,Q,x,y))
                return false;
        }    
    }
    return true;    
}
inline bool pd(int s)
{
    if(cnt[s]<2) return false;
    for(int i=1;i<=n;++i)//
    {
        if(s&(1<<(i-1)))
        {
            bool flag=false;
            for(int j=1;j<=n;++j)
            {
                if(i!=j&&(s&(1<<(j-1)))&&isgo[i][j])
                {
                    flag=true;
                    break;
                }
            }
            if(!flag) return false;
        }
    }
    return true;
}
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i) scanf("%lld%lld",&p[i].x,&p[i].y);
        for(int i=1;i<=m;++i) 
            for(int j=0;j<4;++j) 
                scanf("%lld%lld",&a[i][j].x,&a[i][j].y);
        for(int i=1;i<=n;++i)  //预处理每个监测点能够看到的关键点
        {
            g[i].reset();
            for(int j=1;j<=m;++j)
                for(int k=0;k<4;++k) 
                    if(check(p[i],a[j][k]))
                        g[i][(j-1)*4+k]=1;  
        } 
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j) 
                if(i!=j) isgo[i][j]=check(p[i],p[j]); 
        for(int s=0;s<(1<<n);++s)//预处理所有的状态是否可行. 
        {
            cnt[s]=0;
            for(int i=1;i<=n;++i) if(s&(1<<(i-1))) cnt[s]++;
            vis[s]=pd(s);
        }
        int ans=INF;
        for(int s=0;s<(1<<n);++s)
        {
            if(!vis[s]||cnt[s]>ans) continue;  
            f.reset();
            for(int i=1;i<=n;++i) 
                if(s&(1<<(i-1))) f|=g[i];
            if(f.count()==(m<<2)) ans=min(ans,cnt[s]);    
        }
        if(ans==INF) puts("No Solution!");
        else printf("%d\n",ans);    
    }
    return 0;
} 

BBQ

其实看到这个题就感觉要用DP,因为这个状态的转移时很明显的,题目都说了要将整个序列变成一个以4为单位的小组,使得每个小组都是\(abba\)的格式,那我们转移的时候就找最后一个4个小组即可。
我们设\(f[i]\)表示前i个都已经转化完毕的最小代价。之后考虑我们转移的长度,什么意思呢?
我们要考虑我们最长要将多长的序列来当作1个去处理。
就是考虑f[i]转移的时候:f[i]=min(f[j]+cost[j+1][i])
这个j怎么考虑,它的范围是多少?还有这个代价怎么考虑?
先考虑范围,直观上长度越长,修改成1组不如修改成多组更优,(实际上是复杂度过不去...)
因为1组就4个,所以感觉这个j的长度应该不会太长.直接一个一个考虑即可。
考虑长度为1的话,直接删掉最优。
考虑长度为2的话,需要填上两个。
考虑长度为3的话,最多需要2次。
考虑长度为4的话,最多需要2次。
考虑长度为5的话,一组最少需要1次,二组最多需要3次。
考虑长度为6的话,一组最少需要2次,二组最多需要4次。
考虑长度为7的话,一组最少需要3次,二组最多需要4次。
考虑长度为8的话,一组最少需要4次,二组最多需要4次。
发现到8组的时候一组最少已经和二组最多持平了,如果严谨的话还可以继续往下写:
考虑长度为9的话,一组最少需要5次,二组最多需要5次。
发现长度只要大于8的话,我们完全可以先使其变成8,这样两个消耗的代价相同,可在8的情况下,二组<=一组,所以我们最多是将7个来当成一组进行处理,这样是能保证正确性的。
确定了j的范围之后就要考虑这个代价怎么计算?
既然它的长度只有7,我们考虑能否暴力去搞,我们发现字母之间并无差别,至于是否相同有关,既然如此我们重新将字母赋值,考虑一个序列abcca的代价,我们将其赋成12331,从前往后对每一个新出现的字母变成数字,这样就变成了数字。考虑最大的情况是1234567,我们数组完全开的下,并且可以写个记忆化搜索写干这个事情。
(你永远可以相信优雅的暴力)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10,INF=0x3f3f3f3f;
int T,n,vis[30],dp[N],f[N],p[10];//f[i]表示i这个状态变到一组的最少代价.
char s[N]; 
inline int get(int d,int now)
{
	if(f[now]!=INF) return f[now];//记搜的结果不用更新,因为是固定的. 
	if(d==1) return f[d]=1;
	else if(d==2) return f[d]=2;
	int num=0,x=now;
	int b[10]={0};
	while(x)
	{
		b[++num]=x%10;
		x/=10;
	}
	if(d==3)
	{
		if(b[1]==b[3]||b[1]==b[2]||b[2]==b[3]) return f[now]=1;
		else return f[now]=2;
	}
	else if(d==4)
	{
		if(b[1]==b[4]&&b[2]==b[3]) return f[now]=0;
		else if(b[1]==b[4]||b[2]==b[3]) return f[now]=1;
		else return f[now]=2;
	}
	else if(d>=5)//长度>=5,我们暴力记忆化搜索. 
	{
		int a[10]={0};
		for(int i=num;i>=1;--i) a[i]=a[i+1]+b[i]*p[i];//做后缀
		int x=0; 
		for(int i=1;i<=num;++i)//依次删除 
		{
			f[now]=min(f[now],get(d-1,x+a[i+1]/10)+1);
			x+=b[i]*p[i];
		} 
		return f[now];
	}
}
inline void solve()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=0;i<=n;++i) dp[i]=INF;
	dp[0]=0;
	for(int i=0;i<=n;++i)//往前推. 
	{
		int now=0,num=0;
		for(int j=1;j<=7&&i+j<=n;++j)
		{
			if(!vis[s[i+j]]) vis[s[i+j]]=++num;
			now=now*10+vis[s[i+j]];
			dp[i+j]=min(dp[i+j],dp[i]+get(j,now));	
		} 
		for(int j=1;j<=7&&i+j<=n;++j) vis[s[i+j]]=0;
	}
	printf("%d\n",dp[n]);
}
int main()
{
//	freopen("1.in","r",stdin);
	p[1]=1;
	for(int i=2;i<=8;++i) p[i]=p[i-1]*10;
	memset(f,0x3f,sizeof f);
	scanf("%d",&T);
	while(T--) solve();
	return 0;
} 

3D Puzzles

占坑待填...

posted @ 2022-08-03 09:52  逆天峰  阅读(134)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//