2021牛客多校第2场 G J K

写在前面:
F 题,简单计算几何题。需要的知识:球的方程,球冠的体积,球之间的位置关系。几何相关知识我一窍不通,有必要学习一下。
I 题,常规搜索题,出题人说是bfs,但我用IDA*做的。

G League of Legends(https://ac.nowcoder.com/acm/contest/11253/G)

标签:DP优化,单调队列
题意:校队的\(n\)位成员的空闲时间可以表示成\([l_i,r_i)\)的一段区间,现在要将他们分成\(k\)组,每个人属于且仅属于其中一组,每组的时间是此组中所有成员的公共时间,要求每组的时间不为\(0\),求各组的时间之和的最大值。若无解,输出\(0\)
\(1 \le k \le n \le 5000, \ 0 \le a < b \le 10^5\)

解法:
如果存在两个人\(i,j\),满足\(i\)包含\(j\),即\(l_i \le l_j \le r_j \le r_i\),那么如果将\(i\)\(j\)放到一组,这组的时间将由\(j\)决定,而与\(i\)无关;若将\(i\)放到另一组,\(i\)只会将另一组的时间压缩,不会有任何好处,还不如和\(j\)同组。因此,如果\(i\)\(j\)不同组,那么\(i\)一定单独为一组。
我们可以先将所有包含其他人的人先剔除掉,这样剩下的人,满足若\(l_i < l_j\),一定有\(r_i < r_j\),且一定有\(l_i \neq l_j\)。那么,按pair的排序方法排序后,放到同一组的人的下标一定是连续的一段。这个性质使得dp成为可能。
\(f[i][j]\)为前\(i\)个人分为\(j\)组的最大时间,那么转移过程为 \(f[k][j-1]+l_{k+1}-r_i \to f[i][j]\),其中\(l_{k+1}>r_i\)。可以考虑单调队列,使得dp的时间复杂度为\(O(NK)\)
现在考虑那些被剔除掉的人。若将这些人中的\(i\)个单独分组,那么一定是选择空闲时间最大的\(i\)个人,设他们时间之和为\(sum[i]\),那么总时间为\(f[][k-i]+sum[i]\)。这部分用前缀和即可\(O(N)\)处理。

这道题经过我近一个小时的思考,已经把dp的式子列了出来,实际上我忽略了题目中每组时间不为0的条件,因此进行了更细致的讨论。我已经发现了剔除掉那些人后剩下的人的性质,但是没想到一组的时间可以用\(l_i-r_j\)这么简单的式子表示,因此卡在了dp的优化上,最终没能做出来这道题。反思自己,应该是没有足够的经验,没有足够的自信,还有自己的急躁使得没有耐心想下去了。果然还是需要保持良好的精神,耐心地思考啊。

#include<bits/stdc++.h>
using namespace std;

#define fst first
#define sed second
typedef pair<int,int> PII;
typedef long long LL;
const int N=5010, K=5010, A=100010;
const LL INF=1e10;

int n, k; 
PII a[N], c[N];
LL e[N], sum[N];
bool v[N];
LL ans;
LL f[N][K];
LL q[N], qf, qt;

bool cmp(const PII &a, const PII &b){
	if(a.fst!=b.fst) return a.fst>b.fst;
	return a.sed<b.sed;
}

int main(){
	cin>>n>>k;
	for(int i=1; i<=n; ++i) scanf("%d%d", &a[i].first, &a[i].second);
	//全部组时间不为0
	//只保留不包含其他区间的
	int minn=A;
	sort(a+1,a+n+1,cmp);
	for(int i=1, x; i<=n; ++i){
		if(a[i].sed>=minn) v[i]=true;
		else minn=a[i].sed;
	}
	int cn=0, en=0;
	for(int i=1; i<=n; ++i){
		if(!v[i]) c[++cn]=a[i];
		else e[++en]=a[i].sed-a[i].fst;
	}
	sort(e+1,e+en+1);
	for(int i=1; i<=n || i<=k; ++i) sum[i]=-INF;
	for(int i=1; i<k && en-i+1>0; ++i) sum[i]=sum[i-1]+e[en-i+1];
	for(int i=1, j=cn; i<j; ++i, --j) swap(c[i],c[j]);
	//dp
	for(int i=1; i<=cn; ++i) for(int j=1; j<=k; ++j) f[i][j]=-INF;
	for(int i=1; i<=cn; ++i) if(c[1].sed>c[i].fst) f[i][1]=c[1].sed-c[i].fst;
	auto ff=[](int x, int j)->LL{return f[x][j]+c[x+1].sed;};
	for(int j=2; j<=k && j<=cn; ++j){
		qf=qt=1;
		q[qt++]=j-1;
		for(int i=j; i<=cn; ++i){
			while(qf<qt && c[q[qf]+1].sed<=c[i].fst) qf++;
			if(qf<qt) f[i][j]=max(f[i][j], ff(q[qf],j-1)-c[i].fst);
			while(qf<qt && ff(q[qt-1],j-1)<=ff(i,j-1)) qt--;
			q[qt++]=i;
		}
	}
	//output
	for(int i=1; i<=k&&i<=cn; ++i){
		if(f[cn][i]>0) ans=max(ans, f[cn][i]+sum[k-i] );
	}
	printf("%lld\n", max(0ll,ans));
}

J Product of GCDs(https://ac.nowcoder.com/acm/contest/11253/J)

标签:数学,质因数分解,快速幂,组合数
题意:给定一个含有\(n\)个元素的可重复集合\(S\),对于所有包含\(k\)个元素的子集\(T\),求gcd(\(T\))之积,结果对\(p\)取模。gcd(\(T\))是指\(T\)中所有数的最大公因数。一共\(t\)组数据。
\(1 \le n \le 40000, 1 \le S_i \le 80000, 1 \le k \le 30, 10^6 \le p \le 10^{14} , 1\le t \le 60\)

解法
我不是按照题解做的。
容易想到质因数分解,对每个质数分别求解。对于一个质数,处理出它在集合中每个数中出现了几次。用\(v[i][j]\)表示第\(i\)个质数在\(S_j\)里出现了几次,之后将\(V[i]\)排序,扫描一遍,运用组合数即可求出答案。
上述方法可行,但是运行太慢,用前缀和等优化后可达到要求。
写代码时遇到的问题:
质因数分解:一是\(O(\sqrt n)\)枚举因数,时间上会慢一点,但是代码短;二是借用pollard rho算法,代码很长,但是速度会快一些。这道题需要分解的数要不就是很小\((S_i<=80000)\),要不就是很少\((p<10^{16}, 但是T\le 60)\),因此用前者最合适。
\(\varphi(p)\):找到\(p\)所有的质因数\(a\),全部都进行 \(p=p/a*(a-1)\)运算
快速幂:由于\(p\)不能保证是质数,因此要用到公式 \(a^b\%p=a^{b\%\varphi(p)+\varphi(p)}\%p\)
大整数乘法:对于\(c=a*b\)的运算,由于\(a\)\(b\)范围是\(<10^{14}\),直接乘会爆long long,因此可以用__int128做中间处理。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef __int128 I128;
const int P=1e7+10, X=80010, N=40010, PRI=700000, K=35;

int read(){
	int s=0, w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') w=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0', ch=getchar();
	return s*w;
}

//prime
bool not_prime[P];
int prime[PRI], pn;
int mp[P];
LL phi;

void getPrime(int n){
	not_prime[1]=true;
	for(int i=2; i<=n; ++i){
		if(!not_prime[i]) prime[pn++]=i;
		for(int j=0; j<pn && i*1ll*prime[j]<=n; ++j){
			not_prime[i*prime[j]]=true;
			if(i%prime[j]==0) break;
		}
	}
}

//main
vector<pair<int,int> > V[X];
int v[80000][20];
int n, k; LL p;
int a[N];
bool big[N][K];
LL c[N][K];
LL sum[N];

void factorNum(){
	pair<int,int> tmp;
	for(int x=2; x<X; ++x){
		int t=x;
		for(int i=2; i*1ll*i<=t; ++i){
			if(t%i==0){
				tmp.first=i; tmp.second=0;
				do{
					tmp.second++;
					t/=i;
				}while(t%i==0);
				V[x].push_back(tmp);
			}
		}
		if(t>1) V[x].push_back(pair<int,int>(t,1));
	}
}

void Factor(int x){
	for(auto&i:V[x]){
		v[mp[i.first]][i.second]++;
		v[mp[i.first]][0]++;
	}
}

I128 Pow(int x, I128 nn){
	if(nn>=phi) nn=nn%phi+phi;
	I128 res=1, tmp=x;
	LL n=nn;
	while(n){
		if(n&1ll) res=res*tmp%p;
		n>>=1ll; 
		tmp=tmp*tmp%p;
	}
	return res;
}

LL calP(LL x){
	LL res=x;
	for(int i=0; i<pn && prime[i]*1ll*prime[i]<=x; ++i){
		if(x%prime[i]==0){
			res=res/prime[i]*(prime[i]-1);
			while(x%prime[i]==0) x/=prime[i];
		}
	}
	if(x>1) res=res/x*(x-1);
	return res;
}

int main(){
	//init
	getPrime(P);
	factorNum();
	for(int i=0; i<pn; ++i){
		mp[prime[i]]=i;
	}
	int t; cin>>t;
	while(t--){
		scanf("%d%d%lld", &n, &k, &p);
		phi=calP(p);
		for(int i=0; i<80000; ++i) for(int j=0; j<19; ++j) v[i][j]=0;
		LL ans=1;
		
		for(int i=1; i<=n+1; ++i){
			for(int j=1; j<=k+1; ++j){
				c[i][j]=0;
				big[i][j]=false;
			}
		}
		//start
		for(int i=1; i<=n; ++i){
			a[i]=read();
			Factor(a[i]);
		}
		for(int i=0; i<=n; ++i){
			c[i][0]=1; 
			for(int j=1; j<=k && j<=i; ++j){
				c[i][j]=c[i-1][j]+c[i-1][j-1];
				big[i][j]=big[i-1][j]|big[i-1][j-1];
				c[i][j]>=phi && (c[i][j]-=phi, big[i][j]=true);
			}
			sum[i]=c[i][k-1]+big[i][k-1]*phi;
		}
		for(int i=1; i<=n; ++i) sum[i]+=sum[i-1];
		//solve
		I128 e=1;
		for(int i=0, n1, n2, j; i<8000; ++i){
			int sz=v[i][0];
			if(sz<k) continue;
			n1=0; n2=0;
			for(j=18; j>=1; --j){
				n2=n1+v[i][j];
				if(n1+v[i][j]>=k){
					n1=k-1;
					break;
				}
				n1=n2;
			}
			for(; j>=1; --j){
				ans=ans* Pow(prime[i], e*j*(sum[n2-1]-sum[n1-1]))%p;
				n1=n2;
				n2=n1+v[i][j-1]; 
			}
		}
		printf("%lld\n", ans);
	}
}

K Stack(https://ac.nowcoder.com/acm/contest/11253/K)

标签:构造
题意:原本有一个数组\(a\),将\(a_1\)\(a_n\)依次添加到单调栈中,记录每次添加后的单调栈大小为\(b\)数组。现在\(a\)数组不见了,\(b\)数组只剩下了\(k\)个位置和对应的值。问能否构造一个\(a\)数组,如果能,输出一个方案。
\(1 \le k \le n \le 10^6\)
解法
首先应想到一个贪心的结论:\(b[i+1]\)\(b[i]\)至多大\(1\),因此当\(b[i+1]\)未知时,最优方案是令\(b[i+1]=b[i]+1\)。这样我们就得到了完整的\(b\)数组。
构造的方法有很多,这里说两种:

方法一:拓扑。
老套路了。若\(a[i]\)\(a[j]\)大,就连一条边\((i \to j)\)。最后求一下拓扑序就好了。

方法二:模拟?
整个\(b\)数组中的最后一个\(1\)的位置(记为\(w_1\))是当前\(a\)数组中最小的,也就是\(a[w_1]=1\);将\(b[w_1]\)去掉,将\(b[w_1+1]\)\(b[n]\)都减去\(1\),再次找到最后一个\(1\)的位置(记为\(w_2\))是当前次小的,也就是\(a[w_2]=2\)......以此类推
可以用递归实现。

这里附上我用的第二种方法的代码:

#include<bits/stdc++.h>
using namespace std;

const int N=1e6+10;

int n, k;
int a[N], b[N];
vector<int> v[N];

void solve(int l, int r, int val, int base){
	if(l>r) return;
	int p=lower_bound(v[val].begin(),v[val].end(),l)-v[val].begin();
	int q=upper_bound(v[val].begin(),v[val].end(),r)-v[val].begin()-1;
	int last=l, sum=r-l+1;
	for(int i=p; i<q; ++i){
		a[v[val][i]]=base+r-v[val][i+1]+1+1;
		solve(v[val][i]+1, v[val][i+1]-1, val+1, a[v[val][i]]);
	}
	a[v[val][q]]=base+1;
	solve(v[val][q]+1,r,val+1,a[v[val][q]]);
}

int main(){
	cin>>n>>k;
	for(int i=1, w, x; i<=k; ++i){
		scanf("%d%d", &w, &x);
		b[w]=x;
	}
	bool flag=true;
	for(int i=1; i<=n&&flag; ++i){
		if(!b[i]){
			b[i]=b[i-1]+1;
		}else{
			if(b[i]-b[i-1]>1) flag=false;
		}
		v[b[i]].push_back(i);
	}
	if(!flag){
		puts("-1");
		return 0;
	}
	solve(1,n,1,0);
	for(int i=1; i<=n; ++i){
		printf("%d ", b[i]);
	}puts("");
	for(int i=1; i<=n; ++i){
		printf("%d ", a[i]);
	}
}
posted @ 2021-09-03 20:27  white514  阅读(54)  评论(0编辑  收藏  举报