2022.11.(12/14/15)题解

D1T1:预计紫

D1T2:预计蓝

D1T3:预计紫

D1T4:预计紫

D2T1:P2322 [HNOI2006]最短母串问题(紫)

D2T2:预计紫

D2T3:预计蓝

D2T4:预计蓝

D3T1:预计蓝

D3T2:预计紫

D3T3:预计紫

D3T4:2480 [SDOI2010]古代猪文(紫)


DAY1:

T1:

题目简述:

构造一棵 n 个叶子的二叉树,父节点到左儿子距离为 1,到右儿子距离为 2,给定一个长度为 n 序列 val,对其每个叶子节点赋值为 vali,求最小的 vali×depi

解:

暂无。


T2:

题目简述:

一个只包含的小写字母的字符串,给定 m 个限制,每个限制要求一个字符串中一个子串要与另一个子串,求问符合条件字符串的个数。

解:

可以看出来的是最后有 k 个必须相同的块,那么答案就为 26k

那么现在问题在于如何处理有多少块。

正常就想到,对于两个子串相等,说明每一位对应相等,将其放入一个并查集即可。

时间复杂度:O(n×m)

考虑优化。

可以知道的是,合并的次数是很少的,所以每次合并会有很多区间相等,如果两个区间相等,实际上就不用搜下去了,那么就想到用哈希判重,哈希值利用并查集编号映射。但是如果一个区间只有一个不同,会导致复杂度再次变高,所以想到分治,每次判断一个区间的子串是否需要合并,如果需要,再折半向下分,直到到边界,就合并这两个,合并利用启发式合并,修改哈希值用树状数组即可。

时间复杂度:O(n×log2(n))

#include<iostream>
#include<cstdio>
#include<vector>
#define int long long
using namespace std;
const int N=1e6+5;
const int mod=1e9+7;
int n,m,f[N],t[N],p[N],vis[N];
vector<int>a[N];
int afind(int x)
{
	if(x==t[x])return x;
	return t[x]=afind(t[x]);
}
inline int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int k)
{
	while(x<=n)
	{
		f[x]+=k;
		f[x]%=mod;
		x+=lowbit(x);
	}
}
int search(int x)
{
	int num=0;
	while(x)
	{
		num+=f[x];
		num%=mod;
		x-=lowbit(x);
	}
	return num;
}
void divide(int l1,int r1,int l2,int r2)
{
	if(((search(r1)-search(l1-1))*p[l2-l1]%mod+mod)%mod==(search(r2)-search(l2-1)%mod+mod)%mod)return;
	if(l1==r1)
	{
		int t1=afind(l1),t2=afind(l2);
		if(a[t1].size()>a[t2].size())swap(t1,t2);
		int len=a[t1].size();
		for(int i=0;i<len;i++)
		{
			update(a[t1][i],(t2-t1)*p[a[t1][i]-1]);
			a[t2].push_back(a[t1][i]);
		}
		t[t1]=t[t2];
		return;
	}
	int mid1=(l1+r1)>>1,mid2=(l2+r2)>>1;
	divide(l1,mid1,l2,mid2);
	divide(mid1+1,r1,mid2+1,r2);
}
signed main()
{
	freopen("3-27.in","r",stdin);
	freopen("dealing.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	p[0]=1;
	for(int i=1;i<=n;i++)p[i]=p[i-1]*N%mod,t[i]=i,update(i,t[i]*p[i-1]),a[i].push_back(i);
	for(int i=1;i<=m;i++)
	{
		int len,x,y;
		scanf("%lld%lld%lld",&len,&x,&y);
		if(x>y)swap(x,y);
		divide(x,len+x-1,y,y+len-1);
	}
	int ans=1;
	for(int i=1;i<=n;i++)if(!vis[afind(i)])vis[afind(i)]=1,ans*=26,ans%=mod;
	printf("%lld",ans);
	return 0;
}

T3:

题目简述:

给定 n 个线段,将其分成 k 个组,求每个组的线段交的和的最大值。

解:

分组问题想到退火。

首先先按照线段从长到短排序,若相等按照左端点排序,那么预处理将前 k1 个线段各自成一组,后 nk+1 个成一组。

这样的好处是可能很快骗到答案,且如果所有区间相等,按照左端点排序可以大大增加正确率。

求线段交答案只需要找到集合中最大的左端点与最小的右端点,这两个直接即是答案。

退火中用 set 记录点所在集合大小大于 1 的所有点,然后退火中找 set 中的点来转移给另一个随机集合即可。

正解貌似是动规。

#include<iostream>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<set>
#define Noir 'C'+'Z'+'B'
#define Diruixiao 0.996
#define WXD 1e-12
#define SuBtitle 300000
#define int long long
using namespace std;
const int N=1e5+5;
const double down=Diruixiao;
const double eps=WXD;
struct node
{
	int name,data;
};
priority_queue<node>q1[N];
bool operator <(node fi,node se)
{
	return fi.data<se.data;
}
struct node2
{
	int name,data;
};
priority_queue<node2>q2[N];
bool operator <(node2 fi,node2 se)
{
	return fi.data>se.data;
}
struct node3
{
	int l,r;
}a[N];
int cmp(node3 fi,node3 se)
{
	if(fi.r-fi.l==se.r-se.l)return fi.l<se.l;
	return fi.r-fi.l>se.r-se.l;
}
int n,k,ans,siz[N],vis[N],sum;
int lim;
set<int>s;
void getto(int x,int y)
{
	//cout<<siz[vis[x]]<<" sadf"<<siz[y]<<endl;
	int t=vis[x];
	while(vis[q1[t].top().name]!=t)q1[t].pop();
	while(vis[q2[t].top().name]!=t)q2[t].pop();
	int num1=0;
	if(q2[t].top().data>q1[t].top().data)num1=q2[t].top().data-q1[t].top().data;
	while(!q1[y].empty()&&vis[q1[y].top().name]!=y)q1[y].pop();
	while(!q2[y].empty()&&vis[q2[y].top().name]!=y)q2[y].pop();
	int num3=0;
	if(!q1[y].empty()&&q2[y].top().data>q1[y].top().data)num3=q2[y].top().data-q1[y].top().data;
	vis[x]=y;
	while(!q1[t].empty()&&vis[q1[t].top().name]!=t)q1[t].pop();
	while(!q2[t].empty()&&vis[q2[t].top().name]!=t)q2[t].pop();
	int num2=0;
	if(!q1[t].empty()&&q2[t].top().data>q1[t].top().data)num2=q2[t].top().data-q1[t].top().data;
	sum+=(num2-num1);
	q1[y].push({x,a[x].l}),q2[y].push({x,a[x].r});
	while(vis[q1[y].top().name]!=y)q1[y].pop();
	while(vis[q2[y].top().name]!=y)q2[y].pop();
	int num4=0;
	if(q2[y].top().data>q1[y].top().data)num4=q2[y].top().data-q1[y].top().data;
	sum+=(num4-num3);
	siz[t]--,siz[y]++;
	if(siz[t]==1)
	{
		while(vis[q1[t].top().name]!=t)q1[t].pop();
		while(vis[q2[t].top().name]!=t)q2[t].pop();
		s.erase(q1[t].top().name);
	}
	s.insert(x);
}
void sa()
{
	double T=SuBtitle;
	while(T>eps)
	{
		if(clock()*1.0/CLOCKS_PER_SEC>1.9)return;
		int y=rand()%n+1,l=*(s.begin()),r=*(--s.end()),num=rand()%(r-l+1)+l;
		int x=*s.lower_bound(num);
		if(x==y)y=rand()%n+1;
		if(lim)lim--,x=k;
		int t=vis[x];
		getto(x,vis[y]);
		if(ans<sum)ans=sum;
		else if(exp((double)(ans-sum)*1.0/T)*RAND_MAX>rand())getto(x,t);
		T*=down;
	}
}
signed main()
{
	freopen("3-38.in","r",stdin);
	//freopen("lunatic.out","w",stdout);
	srand(Noir);
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i].l,&a[i].r);
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<k;i++)q1[i].push({i,a[i].l}),q2[i].push({i,a[i].r}),ans+=a[i].r-a[i].l,siz[i]=1,vis[i]=i;
	for(int i=k;i<=n;i++)q1[k].push({i,a[i].l}),q2[k].push({i,a[i].r}),vis[i]=k,s.insert(i);
	siz[k]=n-k+1;
	lim=(n-k+1)/2;
	if(q2[k].top().data>q1[k].top().data)ans+=q2[k].top().data-q1[k].top().data;
	if(n==k)
	{
		printf("%lld",ans);
		return 0;
	}
	sum=ans;
	while(clock()*1.0/CLOCKS_PER_SEC<1.9)sa();
	printf("%lld",ans);
	return 0;
}
/*
10 4
9 10
3 4
1 3
2 8
2 6
1 7
1 6
6 8
6 8
6 8
*/

T4:

题目简述:

给定一个初始为 0 的邻接矩阵,dis(i,j)=ai,j+aj,i,给定 q 次操作,每次对 (x1,y1) 为左下角 (x2,y2) 为右上角这个区间加上一个数,求最小生成树。(矩阵边长范围在 1e5

解:

不会。


DAY2:

T1:

题意简述:

给定 n 个字符串,求一个长度最小情况下字典序最小的包含这 n 个字符串的字符串。

解:

状压字符串,在 failtree 跑广搜,若包含了所有字符串就输出即可。

#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#include<cstdlib>
using namespace std;
const int N=13;
const int M=51;
int n,t[N*M][26],cnt,state[N*M],fail[N*M],vis[N*M][1<<(N-1)],ans[N*M];
char st[M];
void insert(int id)
{
	int len=strlen(st+1),x=0;
	for(int i=1;i<=len;i++)
	{
		if(!t[x][st[i]-'A'])t[x][st[i]-'A']=++cnt;
		x=t[x][st[i]-'A']; 
	}
	state[x]|=(1<<id);
}
queue<int>q;
void getfail()
{
	for(int i=0;i<26;i++)if(t[0][i])q.push(t[0][i]);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=0;i<26;i++)
		{
			if(t[x][i])
			{
				fail[t[x][i]]=t[fail[x]][i];
				state[t[x][i]]|=state[fail[t[x][i]]];
				q.push(t[x][i]);
			}
			else t[x][i]=t[fail[x]][i];
		}
	}
}
struct node
{
	int name,data,pre;
	char ch;
}s[N*M*(1<<(N-1))];
void bfs()
{
	int r=0;
	s[++r]={0,0,0,0};
	vis[0][0]=1;
	int cnt=1;
	while(cnt<=r)
	{
		int x=s[cnt].name,dat=s[cnt].data;
		//cout<<dat<<" "<<(1<<n)-1<<endl;
		if(dat==(1<<n)-1)
		{
			int p=cnt,rt=0;
			while(p)
			{
				ans[++rt]=(int)s[p].ch;
				p=s[p].pre;
			}
			for(int i=rt-1;i>0;i--)putchar(ans[i]);
			exit(0);
		}
		for(int i=0;i<26;i++)
		{
			int xx=t[x][i],data=dat|state[t[x][i]];
			if(vis[xx][data])continue;
			vis[xx][data]=1;
			s[++r]={xx,data,cnt,(char)(i+'A')};
		}
		cnt++;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%s",st+1),insert(i-1);
	getfail();
	bfs();
	return 0;
}

T2:

题意简述:

给定长度为 n 的序列,给定 m 次询问,求 i[l,r]aimodk 的最大值。

解:

分块。

预处理出来每个分块的所有 k 的答案,由于在 [a×k,a×(k+1)1] 中数越大答案就越大,所以只要求出小于 a×(k+1)1 的最大值即可,可以先把块内的所有数放入桶内,求个前缀最大值,即可得到小于等于一个数的最大的数。

查询就正常分块即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e5+5;
const int M=320;
int n,m,a[N],ans[M][N],vis[N],p[N],k;
int main()
{
	freopen("flower0.in","r",stdin);
	freopen("flower.out","w",stdout);
	scanf("%d%d",&n,&m);
	k=sqrt(n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),p[i]=(i-1)/k+1;
	for(int i=1;i*(k-1)+1<=n;i++)
	{
		for(int j=(i-1)*k+1;j<=min(i*k,n);j++)vis[a[j]]=a[j];
		for(int j=1;j<=N-5;j++)vis[j]=max(vis[j],vis[j-1]);
		for(int j=1;j<=N-5;j++)for(int t=0;t<=N-5;t+=j)ans[i][j]=max(ans[i][j],vis[min(N-5,t+j-1)]-t);
		for(int j=1;j<=N-5;j++)vis[j]=0; 
	}
	while(m--)
	{
		int l,r,t;
		scanf("%d%d%d",&l,&r,&t);
		int res=0;
		for(int i=l;i<=min(r,p[l]*k);i++)res=max(res,a[i]%t);
		for(int i=p[l]+1;i<p[r];i++)res=max(res,ans[i][t]);
		for(int i=max(l,(p[r]-1)*k+1);i<=r;i++)res=max(res,a[i]%t);
		printf("%d\n",res);
	}
	return 0;
}

T3:

题意简述:

给定 n 个二元组,选取任意个,求 y_i<y_{i-1}&(x_{i-2}>x_i>x_{i-1}|x_{i-1}>x_i>x_{i-2}) 的方案数。

解:

正常人会想按照 y 排序去 dp,这个的确可以拿很高的部分分,但是想拿正解是不好优化的。

所以应该按照 x 轴排序,根据画图,我们发现上述的限制像一个折线,而如果新加入一个在集合中最大的 x 的点,那么要不是折线最上面,要不就是第二高的。

想出用 dp,设 dpi,2 代表第 i 个点下一步向右/左拐的方案数,那么如果这是最上面的点,即 yi>yj,那么

dpi,1=dpi,1+dpj,0

否则:

dpj,0=dpj,0+dpi,1

由于其实舍去了一位,舍去了下一个端点的编号,利用背包思想,可以倒序枚举处理后效性。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int mod=1000000007;
const int N=6006;
int n,ans;
int f[N][2];
struct node
{
	int x,y;
}a[N];
int cmp(node fi,node se)
{
	return fi.x<se.x;
}
int read()
{
	char ch=getchar();
	int f=1,sum=0;
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		sum=(sum<<1)+(sum<<3)+(ch^48);
		ch=getchar();
	}
	return sum*f;
}
signed main()
{
	freopen("refract.in","r",stdin);
	freopen("refract.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read();
	sort(a+1,a+1+n,cmp);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		f[i][0]=f[i][1]=1;
//		cout<<"fuck you";
		for(int j=i-1;j>0;j--)
		{
			if(a[i].y<a[j].y)f[j][0]+=f[i][1],f[j][0]%=mod;
			else f[i][1]+=f[j][0],f[i][1]%=mod;
		}
	}
	for(int i=1;i<=n;i++)
	{
		ans+=f[i][0];
		ans%=mod;
		ans+=f[i][1];
		ans%=mod;
		ans--;
		ans=(ans%mod+mod)%mod;
	}
	printf("%d",ans);
	return 0;
}

T4:

题意简述:

给定一个初始全为 0 的矩阵,将这个矩阵一个任意联通块染成 01,求染成目标矩阵的最少步数。

解:

可以想到,我们先把所有目标该染成黑色的染成黑色,再处理那些不应该是黑色的。

想到二分图,可是发现不行,所以就想到能不能贪心去求。

对于每一个边连通分量出发,能走到最远的与自己颜色相同的通分量即是要染色的次数。

最后取最小值,至于正确性,我们可以把走路看成对这一块与从之前走过来的整条路径染色,那么改变了 disi 次一定可以达到目标颜色,这个易证。

#include<iostream>
#include<cstdio>
#include<deque>
#define int long long
using namespace std;
const int N=55;
int n,m,a[N][N],col[N][N],color,f[4][2]={{1,0},{0,1},{-1,0},{0,-1}},cnt,ans=1e9,dis[N*N],vis[N][N];
void dfs(int x,int y,int c)
{
	col[x][y]=c;
	for(int i=0;i<4;i++)
	{
		int xx=x+f[i][0],yy=y+f[i][1];
		if(xx<1||xx>n||yy<1||yy>m||col[xx][yy]||a[x][y]!=a[xx][yy])continue;
		dfs(xx,yy,c);
	}
}
struct node
{
	int x,y,data;
};
deque<node>q;
void bfs(int xb,int yb)
{
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)vis[i][j]=0;
	for(int i=1;i<=color;i++)dis[i]=1e9;
	q.push_back({xb,yb,1});
	dis[col[xb][yb]]=1;
	while(!q.empty())
	{
		int x=q.front().x,y=q.front().y,dat=q.front().data;
		vis[x][y]=1;
		q.pop_front();
		for(int i=0;i<4;i++)
		{
			int xx=x+f[i][0],yy=y+f[i][1];
			if(xx<1||yy<1||xx>n||yy>m)continue;
			if(col[x][y]==col[xx][yy]&&!vis[xx][yy])q.push_front({xx,yy,dat});
			if(col[x][y]!=col[xx][yy]&&dat+(a[xx][yy]^a[x][y])<dis[col[xx][yy]])
			{
				dis[col[xx][yy]]=dat+(a[xx][yy]^a[x][y]);
				if(a[xx][yy]==1)q.push_front({xx,yy,dis[col[xx][yy]]});
				else q.push_back({xx,yy,dis[col[xx][yy]]});
			}
		}
	}
	int res=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)if(a[i][j])res=max(res,dis[col[i][j]]);
	}
	ans=min(ans,res);
}
signed main()
{
	freopen("paint.in","r",stdin);
	freopen("paint.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%1lld",&a[i][j]);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!col[i][j])dfs(i,j,++color);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)bfs(i,j);
	printf("%lld",ans);
	return 0;
}

DAY3:

T1:

题意简述:

给定一串初始为 0 的串,给定第 s 位为 1,可以将序列一个长度为 k 的区间倒过来,给定一些禁区表示 1 不能落入这些禁区,求到达每个点的最小翻转次数。

解:

很容易想到广搜,至于怎么优化,先无效性剪枝一波,再无效性剪枝一波,最后再无效性剪枝一波,然后就卡过去了……

正解用 set 套取优化,主要是处理广搜搜索下一个点时很多不需要枚举的问题,将没搜到的奇数点与偶数点分别存一个 set,最后在枚举时只用考虑
set 中的数即可。

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int N=1e5+5;
int n,k,m,s,a[N],ans[N],vis[N],num;
struct node
{
	int name,data;
};
queue<node>q;
int read()
{
	char ch=getchar();
	int sum=0;
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
	return sum;
}
void write(int x)
{
	if(x/10)write(x/10);
	putchar((x%10)^48);
}
void bfs()
{
	for(int i=1;i<=n;i++)ans[i]=2e9;
	q.push({s,0});
	ans[s]=0;
	vis[s]=1;
	int sum=0;
	while(!q.empty())
	{
		int x=q.front().name,dat=q.front().data;
		ans[x]=dat;
		sum++;
		if(sum==num)break;
		q.pop();
		if(!(x-k-2>0&&ans[x-2]<1e9))
		{
			for(int i=k%2+1,num=k/2-1;i<=k;i+=2,num--)
			{
				if(x-i-num<1)break;
				if(x+num>n)
				{
					i+=(num-(n-x))*2;
					num=n-x;
				}
				if(!a[x-i]&&!vis[x-i])
				{
					vis[x-i]=1;
					q.push({x-i,ans[x]+1});
				}
			}
		}
		if(!(x+k+2<=n&&ans[x+2]<1e9))
		{
			for(int i=k%2+1,num=k/2-1;i<=k;i+=2,num--)
			{
				if(x+i+num>n)break;
				if(x-num<1)
				{
					i+=(num-(x-1))*2;
					num=x-1;
				}
				if(!a[x+i]&&!vis[x+i])
				{
					vis[x+i]=1;
					q.push({x+i,ans[x]+1});
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(ans[i]>1e9)putchar('-'),putchar('1');
		else write(ans[i]);
		if(i!=n)putchar(' ');
	}
}
int main()
{
	freopen("reverse.in","r",stdin);
	freopen("reverse.out","w",stdout);
	n=read(),k=read(),m=read(),s=read();
	for(int i=1;i<=m;i++)
	{
		int x;
		x=read();
		a[x]=1;
	}
	for(int i=1;i<=n;i++)num+=(a[i]^1);
	bfs();
	return 0;
}

T2:

题目简述:

给定正视图与左视图,求方块拜放方案数,正视图与左视图用数字代表高度。

首先可以将正视图与左视图的高度排个序,是不会影响结果的,因为实际上我们需要的是对于每一行每一列的最大值都对应的正视图与左视图的一个数,所以改变数位置在每种情况只需要交换行或列可以使其仍然满足条件。

排了序后,对于横竖交错的格子如果对应的两个数相同,那么这一类格子我们可以用组合数求加容斥求。

首先用组合数求出所有的情况,再先减去至少一行不符合条件,加上至少两行符合条件的情况,这些步骤就是容斥,求遍容斥即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int N=1e5+5;
const int mod=1000000007;
int n,a[N],b[N],vis1[N],vis2[N],t[N],power[2*N],p[2*N],q[2*N];
struct node
{
	int name,data;
}num[2*N];
int cmp(node fi,node se)
{
	return fi.data<se.data;
}
int read()
{
	char ch=getchar();
	int sum=0;
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
	return sum;
}
void write(int x)
{
	if(x/10)write(x/10);
	putchar((x%10)^48);
}
int quick_pow(int x,int y)
{
	int sum=1,num=x;
	while(y)
	{
		if(y&1)sum*=num,sum%=mod;
		num*=num;
		num%=mod;
		y>>=1;
	}
	return sum;
}
int solve(int x,int y,int lenx,int leny,int num)
{
	int tot=0;
	for(int i=0;i<=lenx;i++)
	{
		int res=quick_pow(((quick_pow(num+1,(x+lenx)-i)-quick_pow(num,(x+lenx)-i))+mod)%mod,leny);
		res*=quick_pow(num+1,(lenx-i)*y)*quick_pow(num,i*(y+leny))%mod;
		res%=mod;
		res*=power[lenx]*p[i]%mod*p[lenx-i]%mod;
		res%=mod;
		if(i&1)tot-=res;
		else tot+=res;
		tot=(tot%mod+mod)%mod;
	}
	return tot;
}
signed main()
{
	//freopen("silhouette.in","r",stdin);
	//freopen("silhouette.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)a[i]=read(),num[i]={i,a[i]};
	for(int i=1;i<=n;i++)b[i]=read(),num[i+n]={i+n,b[i]};
	sort(num+1,num+1+2*n,cmp);
	int cnt=0;
	num[0].data=-1;
	for(int i=1;i<=2*n;i++)
	{
		if(num[i].data!=num[i-1].data)q[++cnt]=num[i].data;
		if(num[i].name<=n)a[num[i].name]=cnt;
		else b[num[i].name-n]=cnt;
	}
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	sort(b+1,b+1+n);
	reverse(b+1,b+1+n);
	int rx=0,ry=0,ans=1;
	power[0]=p[0]=1;
	for(int i=1;i<=2*n;i++)power[i]=power[i-1]*i,p[i]=quick_pow(power[i],mod-2);
	for(int i=cnt;i>=1;i--)
	{
		int lx=rx,ly=ry;
		while(a[rx+1]==i)rx++;
		while(b[ry+1]==i)ry++;
		ans*=solve(lx,ly,rx-lx,ry-ly,q[i]);
		ans%=mod;
	}
	write(ans);
	return 0;
}

T3:

题目简述:

n+2 个座位,一来有两个人坐在 0n+1 上,剩下的人要离其他人尽可能远,求每个人坐每个位置的概率。

解:

由于当距离为一个值时初始的区间长度集合一定是固定的,所以可以先预处理出来这个集合,然后分为偶数区间与奇数区间处理,中间设 fi,j 代表距离为 i 偶区间为 j 个的概率,利用这个加上组合数求出每个人坐座位的方案数,因为前面的人选偶数区间会影响下面的人,所以从小往大枚举,如果一次要选偶数区间,那么将后面的区间对称求解即可。

(不要怀疑,就是赶出来的题解)

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int N=1050;
int n,mod,p[N],vis[N],pos[N],cnt[N],even[N],f[N][N],dp[N][N],g[N][N];
int quick_pow(int x,int y)
{
	int sum=1,num=x;
	while(y)
	{
		if(y&1)sum*=num,sum%=mod;
		num*=num;
		num%=mod;
		y>>=1;
	}
	return sum;
}
signed main()
{
//	freopen("seat.in","r",stdin);
	//freopen("seat.out","w",stdout);
	scanf("%lld%lld",&n,&mod);
	for(int i=0;i<=n;i++)p[i]=quick_pow(i,mod-2);
	for(int i=1;i<=n;i++)
	{
		int lc=1,rc=0,l=1;
		for(int j=1;j<=n;j++)
		{
			if(vis[j])l=j+1;
			if(j-l+1>rc-lc+1)rc=j,lc=l;
		}
		int mid=(lc+rc)>>1,len=(rc-lc+2)>>1;
		pos[i]=mid;
		cnt[len]++;
		vis[mid]=1;
		if((rc-lc+1)%2==0)even[len]++;
	}
	int lim=n;
	for(int i=1;i<=n;i++)
	{
		if(!cnt[i])continue;
		//cout<<i<<" "<<cnt[i]<<endl;
		int l=lim-cnt[i]+1,r=lim,t=l+even[i]-1;
		lim=l-1;
		if(i==1)
		{
			for(int j=l;j<=r;j++)for(int k=l;k<=r;k++)dp[j][pos[k]]=p[cnt[i]];
			continue;
		}
		for(int j=0;j<=cnt[i];j++)for(int k=0;k<=even[i];k++)f[j][k]=0;
		f[0][even[i]]=1;
		for(int j=1;j<=cnt[i];j++)
		{
			int sumodd=0,sumeven=0;
			for(int k=0;k<=even[i];k++)
			{
				if(!f[j-1][k])continue;
				if(k)
				{
					int num=f[j-1][k]*k%mod*2%mod*p[cnt[i]-j+1+k]%mod;
					f[j][k-1]+=num;
					f[j][k-1]%=mod;
					sumeven+=num*p[even[i]*2];
					sumeven%=mod;
				}
				if(cnt[i]-j+1>k)
				{
					int num=f[j-1][k]*((cnt[i]-j+1-k)%mod+mod)%mod*p[cnt[i]-j+1+k]%mod;
					f[j][k]+=num;
					f[j][k]%=mod;
					sumodd+=num*p[cnt[i]-even[i]]%mod;
					sumodd%=mod;
				}
			}
			for(int k=l;k<=t;k++)
			{
				dp[l+j-1][pos[k]]+=sumeven,dp[l+j-1][pos[k]]%=mod;
				dp[l+j-1][pos[k]+1]+=sumeven,dp[l+j-1][pos[k]+1]%=mod;
			}
			for(int k=t+1;k<=r;k++)dp[l+j-1][pos[k]]+=sumodd,dp[l+j-1][pos[k]]%=mod;
		}
		for(int j=l;j<=t;j++)
		{
			int lc=pos[j]-i+1,rc=pos[j]+i;
			for(int k=lc;k<=rc;k++)
			{
				if(k==pos[j])continue;
				int q=rc-(k-lc);
				if(k>pos[j])q=lc+(rc-k);
				for(int s=r+1;s<=n;s++)
				{
					g[s][k]+=dp[s][k]*p[2];
					g[s][k]%=mod;
					g[s][q]+=dp[s][k]*p[2];
					g[s][q]%=mod;
				}
			}
			for(int k=lc;k<=rc;k++)for(int s=r+1;s<=n;s++)dp[s][k]=g[s][k],g[s][k]=0;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)printf("%lld ",dp[i][j]);
		printf("\n");
	}
	return 0;
}

T4:

题目描述:

给定 n,求所有的 i 使得 nmodi=0Cni 的和 sum,再求 gsummod999911659

解:

分解 999911658,对其每个因数求次答案,求组合数时可以用 Lucas,然后用 CRT 求通解,最后费马小定理加快速幂求答案。

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int mod=999911659;
int n,g,a[4]={2,3,4679,35617},power[35618][4],t[4];
int quick_pow(int x,int y,int p)
{
	int sum=1,num=x;
	while(y)
	{
		if(y&1)sum*=num,sum%=p;
		num*=num;
		num%=p;
		y>>=1;
	}
	return sum;
}
int C(int x,int y,int p,int id)
{
	if(x<y)return 0;
	return power[x][id]*quick_pow(power[y][id],p-2,p)%p*quick_pow(power[x-y][id],p-2,p)%p;
}
int Lucas(int x,int y,int p,int id)
{
	if(x<y)return 0;
	if(x<p)return C(x,y,p,id)%p;
	return C(x%p,y%p,p,id)*Lucas(x/p,y/p,p,id)%p;
}
int CRT()
{
	int sum=0;
	for(int i=0;i<4;i++)sum+=t[i]*(mod-1)/a[i]%(mod-1)*quick_pow((mod-1)/a[i],a[i]-2,a[i])%(mod-1),sum%=(mod-1);
	return sum;
}
signed main()
{
	freopen("ancient.in","r",stdin);
	freopen("ancient.out","w",stdout);
	scanf("%lld%lld",&n,&g);
	if(g%mod==0)
	{
		printf("0");
		exit(0);
	}
	power[0][0]=power[0][1]=power[0][2]=power[0][3]=1;
	for(int i=1;i<=35617;i++)for(int j=0;j<4;j++)power[i][j]=power[i-1][j]*i%a[j];
	for(int i=1;i*i<=n;i++)
	{
		if(n%i==0)
		{
			for(int j=0;j<4;j++)t[j]+=Lucas(n,i,a[j],j),t[j]%=a[j];
			if(i*i!=n)for(int j=0;j<4;j++)t[j]+=Lucas(n,n/i,a[j],j),t[j]%=a[j];
		}
	}
	int ans=quick_pow(g,CRT(),mod);
	printf("%lld",ans);
	return 0;
}
posted @   Gmt丶Fu9ture  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示