HGOI8.22集训题解

题解

暴力骗分真奇妙

这里写图片描述


第一题——函数返回值(fun)

【题目描述】

给出T个n,求出公式

$f(n)=\sum_{1\leq i \leq j \leq n}^{lcm(i,j)==n}1$

  • 纯暴力,30分。
  • 可以通过规律发现, l c m ( i , j ) = i j / g c d ( i , j ) lcm(i,j)=ij/gcd(i,j) lcm(i,j)=ij/gcd(i,j),那么单纯打暴力可以先进行因数分解优化,拿到60分。
  • 给出的数据是 n ≤ 1 0 14 n\leq 10^{14} n1014,那么素数筛筛到 1 0 7 10^7 107,但是还是会爆时间,那就只有利用Miller-robin或者减少欧拉筛的范围。
  • 通过打表发现,答案只与质因数的个数有关。 n = ∏ p i k i n=\prod p_i^{k_i} n=piki,而 f ( n ) = ∏ ( 2 ∗ k i + 1 ) 2 f(n)=\frac{\prod (2*k_i+1)}{2} f(n)=2(2ki+1)
  • 主要是筛质因数,其他都很简单,不会被卡。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#define LL long long 
using namespace std;
void fff(){
	freopen("fun.in","r",stdin);
	freopen("fun.out","w",stdout);
}
LL n;
LL prime[1000000],p_num=0;
bool visited[10000001];
LL m[202000],cnt;
LL fun(LL n){
	LL res=0,t=1;
	memset(m,0,sizeof(m));
	cnt=0;
	for (int i=1;i<=p_num;i++){
		if(n%prime[i]==0){
			cnt++;
			while(n%prime[i]==0){
				m[cnt]++;
				n/=prime[i];
			}
			t*=(2*m[cnt]+1);
		}
		if(n==1) break;
	}
	if(n>1) m[++cnt]=n,t*=3;
	return (t+1)/2;
}
int main(){
	fff();
	int T;scanf("%d",&T);
	for (int i=2;i<=7000000;i++){
		if(!visited[i]) prime[++p_num]=i;
		for (int j=1;j<=p_num&&i*prime[j]<=10000000;j++){
			visited[prime[j]*i]=true;
			if(i%prime[j]==0) break;
		}
	}
	for(int j=1;j<=T;j++){
		scanf("%lld",&n);
		printf("Case %d: %lld\n",j,fun(n));
	}
}

第二题——最长子序列(longseq)

【题目描述】

  • 再给出序列当中求出最长非连续子序列,元素大小不超过8,要求满足:
    • 1、任意两个元素之间的个数绝对值之差不超过1,没出现的算0次。
    • 2、相同的元素必须连续。

  • 很明显是一个dp题,考场上没做出来打了把假的dp,骗了50分。
  • 可以用二进制来标记这个数字是否有出现,二分元素至少出现的长度,那么就可以在这个长度和长度+1的情况下浮动,记录下元素的位置进行深搜,记忆化剪枝
  • 上面全是废话,talk is treap, take the view of the code!。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
void fff(){
	freopen("longseq.in","r",stdin);
	freopen("longseq.out","w",stdout);
}
const int N=1010,MM=(1<<8);
int INF;
int n,ans,mark;
int a[N];
int f[N][MM];
vector <int> v[9];
int dfs(int i,int S,int x){
	if(f[i][S]!=INF) return f[i][S];
	if(i>n){
		if(S==MM-1) return 0;
		return INF;
	}
	int ret=INF;
	if(((S>>a[i])&1)==0){
		int pos=lower_bound(v[a[i]].begin(),v[a[i]].end(),i)-v[a[i]].begin();
		if(pos+x-1<v[a[i]].size()){
			ret=max(ret,x+dfs(v[a[i]][pos+x-1]+1,S|(1<<a[i]),x));
		}
		if(pos+x<v[a[i]].size()){
			ret=max(ret,x+1+dfs(v[a[i]][pos+x]+1,S|(1<<a[i]),x));
		}
	}
	ret=max(ret,dfs(i+1,S,x));
	return f[i][S]=ret;
}
bool ok(int mid){
	memset(f,-0x3f,sizeof(f));
	INF=f[0][0];
	int tmp=dfs(1,0,mid);
	ans=max(ans,tmp);
	if(tmp>0) return true;
	return false;
}
int main(){
	fff();
	scanf("%d",&n);
	ans=0;
	for (int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		a[i]--;
		if(!((mark>>a[i])&1)) mark=mark|(1<<a[i]),ans++;
		v[a[i]].push_back(i);
	}
	int l=1,r=n/8;
	while (l<=r){
		int mid=(l+r)>>1;
		if(ok(mid)) l=mid+1;
		else r=mid-1;
	}
	cout<<ans;
}

第三题——树上路径(tree)

【题目描述】

  • 给出一棵树,m组查询,求在当前组查询之前有多少组路径与其重叠,重叠只要有一个节点重复就算是合法。

  • 重叠就可以知道是当前的lca在之前的某组的链上(lca和a或b上),或者之前的某组的lca在当前的链上。

  • 然后如果暴力维护,也是 O ( m 2 ) O(m^2) O(m2),难为倍增沦落为暴力orz

  • 用dfn序和树状数组进行维护

  • 考虑上面的两种情况,第一种情况是他包我(有点猥琐啊…),那么我们在dfn[lca]处+1,dfn[lca]+size[lca]处-1,求和的时候就是求出子树和,那么经过lca的所有节点都会做出贡献,也就是会在改区间内+1。

  • 第二种情况就是我包他(还是很猥琐)。那就类似于树上差分的样子,在dfn[u],dfn[v]处+1,在dfn[lca]处-2。然后在求出区间内的存在的子树的和(dfn[u]-dfn[v]之间的和)就可以了。

  • 由于以上两种会把lca相同的情况重复算到,那就单独记录。


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
void fff(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
}
inline int read(){
	char ch=getchar();
	int x=0;
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
const int N=200010;
struct Edge{
	int nxt,to;
}e[N<<1];
int head[N],tot=0;
int L[N],R[N];
int t1[N],t2[N];
inline void add(int u,int v){
	e[++tot].nxt=head[u];
	e[tot].to=v;
	head[u]=tot;
}
int father[N][20],depth[N],ans,app[N];
int n,m,stm=0;
bool visited[N];
int lowbit(int x){return x&(-x);}
int sum(int *tr,int x){int ret=0;for(;x;x-=lowbit(x)) ret+=tr[x];return ret;}
void change(int *tr,int x,int v){
	for(;x<=n;x+=lowbit(x)) tr[x]+=v;
}
void dfs(int u){
	visited[u]=true;
	L[u]=++stm;
	for (int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(!visited[v]){
			depth[v]=depth[u]+1;
			father[v][0]=u;
			dfs(v);
		}
	}
	R[u]=stm;
}
void st(){
	for(int j=1;j<20;j++)
		for (int i=1;i<=n;i++)
			father[i][j]=father[father[i][j-1]][j-1];
}
int get_lca(int x,int y){
	if(depth[x]<depth[y]) swap(x,y);
	for(int i=19;i>=0;i--){
		if(depth[father[x][i]]>=depth[y]){
			x=father[x][i];
		}
	}
	if(x==y) return x;
	for(int i=19;i>=0;i--){
		if(father[x][i]!=father[y][i]){
			x=father[x][i];
			y=father[y][i];
		}
	}
	if(father[x][0]!=father[y][0]){
		x=father[x][0];
		y=father[y][0];
	}
	return father[x][0];
}
void calcans(int x,int y,int lca){
	ans+=sum(t1,R[lca])-sum(t1,L[lca]-1);
	ans+=sum(t2,L[x])+sum(t2,L[y])-sum(t2,L[lca])*2;
}
int main(){
	fff();
	n=read();
	for (int i=1;i<n;i++){
		int u,v;
		u=read(),v=read();
		add(u,v),add(v,u);
	}
	depth[1]=1;
	dfs(1);
	st();
	m=read();
	for (int i=1;i<=m;i++){
		int u,v;
		u=read(),v=read();
		int g=get_lca(u,v);
		ans=0;
		calcans(u,v,g);
		printf("%d\n",ans+app[g]);
		app[g]++;
		change(t1,L[u],1);
		change(t1,L[v],1);
		change(t1,L[g],-2);
		change(t2,L[g],1);
		change(t2,R[g]+1,-1);
	}
	
}

第四题——附加题:网格填数(grid)

【题目描述】

  • 给出每行每列的1块的限制个数,1块是指连续的1,求出有多少个m*n的图,满足每行每列满足限制。

  • 暴力剪枝=50分
  • 考虑到n最多就5,m最多就20,那么m上的块最多就10个,那就可以hash压缩状态。
  • 做的时候不是一个一个做,而是一列一列做。预处理好所有合法的列,然后再塞进去判断是否满足行的合法情况。
  • 只要知道当前的hash值和上一层的hash值,就可以更新hash。
  • 状态满足f[now,pre,hash],记忆化搜索。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
void fff(){
	freopen("grid.in","r",stdin);
	freopen("grid.out","w",stdout);
}
const int MOD=1e9+7;
int n,m,ans=0,mx;
int a[30],b[30];
int d[170000],d1;
int s1[30],s[30][30];
int f[30][20][170000];
void dfs(int x,int y){
	if(x==0){
		d[++d1]=y;
		return;
	}
	for(int i=0;i<=a[x];i++) dfs(x-1,y*11+i);
}
int work(){
	for (int i=0;i<1<<n;i++){
		int j=i,last=0,tot=0;
		while(j){
			if(j%2&&!last) tot++;
			last=j&1;
			j=j>>1;
		}
		s1[tot]++;
		s[tot][s1[tot]]=i;
	}
	dfs(n,0);
}
bool check(int x,int y,int z){
	for(int i=1;i<=n;i++){
		int w=x%11;
		if((m-y+1)/2<a[i]-w||!w&&(z&1)) return 0;
		if(w>(y+1)/2) return 0;
		x/=11;
		z>>=1;
	}
	return 1;
}
int main(){
	fff();
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=n;i>=1;i--) mx=mx*11+a[i];
	for (int i=1;i<=m;i++) scanf("%d",&b[i]);
	work();
	for (int i=1;i<=s1[b[1]];i++){
		int k=0,w=s[b[1]][i],r[6];
		for(int j=1;j<=n;j++){
			r[j]=w&1;
			w=w>>1;
		}
		for(int j=n;j>=1;j--) k=k*11+r[j];
		f[1][i][k]++;
	}
	for(int i=2;i<=m;i++)
		for(int j=1;j<=s1[b[i]];j++){
			for(int k=1;k<=d1;k++){
				if(!check(d[k],i,s[b[i]][j])) continue;
				for(int l=1;l<=s1[b[i-1]];l++){
					int k1=d[k],x=s[b[i]][j],y=s[b[i-1]][l],k2=0,r[6];
					for(int w=1;w<=n;w++){
						if((x&1)==1&&(y&1)==0) r[w]=k1%11-1;
						else r[w]=k1%11;
						x=x>>1,y=y>>1;
						k1/=11;
					}
					for(int w=n;w>=1;w--) k2=k2*11+r[w];
					f[i][j][d[k]]+=f[i-1][l][k2];
					f[i][j][d[k]]%=MOD;
				}
			}
		}	
	int ans=0;
	for(int i=1;i<=s1[b[m]];i++) ans=(ans+f[m][i][mx])%MOD;
	cout<<ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值