题解
暴力骗分真奇妙
第一题——函数返回值(fun)
【题目描述】
给出T个n,求出公式
- 纯暴力,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} n≤1014,那么素数筛筛到 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∏(2∗ki+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;
}