Problem1 单峰
小X 归来后,首先对数列很感兴趣。他想起有1类特殊的数列叫单峰数列。 我们说一个数列 {ai} 是单峰的,当且仅当存在一个位置 k 使得 ai < ai+1(i < k) 且 ai > ai+1(i ≥ k)。 现在小X 想知道,对于 1 到 n 的所有排列,其中有多少个是单峰数列。 Input 第1行包含1个整数 n。 Output 第一行包含一个整数,表示答案除以 1e9 + 7 的余数。 Example
unimodal.in | unimodal.out |
2 | 2 |
Scoring • 对于 20% 的数据, n ≤ 10。 • 对于 50% 的数据, n ≤ 105。 • 对于 100% 的数据, 2 ≤ n ≤ 1018。
解析:
1.1 20 分做法
生成所有全排列并判断,时间复杂度 O(n · n!)。
1.2 50 分做法
根据排列组合可以发现,峰顶一定是 n,因此考虑 1 ∼ n - 1 分别放在 n 的左边还是右边,一一得出相应
的唯一方案。所以答案就是 2^(n-1),时间复杂度 O(n)。
1.3 100 分做法
对2^(n-1)用快速幂即可,时间复杂度 O(log n)。
代码:
#include<bits/stdc++.h> using namespace std; template <class T> inline void readl(T &x) { x=0;bool f=0;char ch=getchar(); while(!isdigit(ch)) { f=(ch==45);ch=getchar();} while( isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?(~x+1):x;} long long n; const int md=1e9+7; inline long long bpow(long long a,long long b) { long long base=a; long long ans=1; while(b) { if(b&1) ans=(ans%md*base%md)%md; base=base*base%md; b>>=1; } return ans%md; } int main() { freopen("unimodal.in","r",stdin); freopen("unimodal.out","w",stdout); readl(n); printf("%lld ",(bpow(2,n-1)%md)); return 0; }
Problem2 积木
小X 感到很无聊,从柜子里翻出了他小时候玩的积木, 这套积木共有 n 块,每块积木都是1个长方体。小X 想用这些积木拼成1个积木塔(不必每块 积木都使用, 所谓积木塔,就是将积木1个1个摞起来,(除去最底层的积木外)每块积木的底下必须能被它下面 的积木的底面完全包含(即对应的长宽都要更小或相等)。当然,积木可以任意放置,即可以以任意一面 作为底面。 现在小X 想知道,积木塔最大能拼多高。 Input 第⼀⾏包含⼀个整数 n。 接下来 n ⾏,每⾏包含三个整数 a; b; c,表⽰该块积木是⼀个 a × b × c 的长⽅体。 Output 第⼀⾏包含⼀个整数,表⽰答案。 Example
brick.in | brick.out |
3 8 7 6 3 9 4 1 10 5 |
18 |
Explanation 选择第 1 块积木和第 3 块积木。
Scoring • 对于 10% 的数据, n = 1。
• 对于 40% 的数据, n ≤ 6。
• 对于 100% 的数据, 1 ≤ n ≤ 15, 1 ≤ a; b; c ≤ 108。
解析:
2.1 10 分做法
输出 maxfa; b; cg。
2.2 40 分做法
生成全排列,然后枚举每个积木哪个面朝上,时间复杂度 O(n! · 3n)。
2.3 100 分做法
显然是状态压缩 DP。设计状态 f[S][i][0/1/2] 表示已经用了集合 S 内的积木,最顶上是编
号为 i 的积木,它的哪个面朝上。转移时枚举不在 S 内的积木,以及朝上的面判断即可。时间
复杂度 O(2n · (3n)2)。
PS:但是这道题的数据有点弱,只要搜索写得好,照样可以过。
代码:
1.DP
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; int readl() { int x=0,fg=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fg=-fg;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*fg; } int n,a[200],b[200],c[200],t[20],mx=-100,bin[200]; ll ans=-100; ll f[(1<<15)+10][16][3]; int main() { freopen("brick.in","r",stdin); freopen("brick.out","w",stdout); n=readl(); for(int i=1;i<=n;i++) { a[i]=readl();b[i]=readl();c[i]=readl(); t[1]=a[i];t[2]=b[i];t[3]=c[i]; sort(t+1,t+4); a[i]=t[1];b[i]=t[2];c[i]=t[3]; mx=max(mx,t[3]); } if(n==1){printf("%d",mx);return 0;} bin[1]=1; for(int i=2;i<=20;i++)bin[i]=bin[i-1]*2; for(int i=1;i<=n;i++) { f[bin[i]][i][0]=a[i]; f[bin[i]][i][1]=b[i]; f[bin[i]][i][2]=c[i]; } //printf("%lld %lld %lld ",f[1][1][0],f[1][1][1],f[1][1][2]); for(int x=0;x<(1<<n);x++) for(int i=1;i<=n;i++) { if(x&bin[i])continue; for(int j=1;j<=n;j++) { if((x&bin[j])==0)continue; int s=x|bin[i]; if(b[j]>=b[i]&&c[j]>=c[i])f[s][i][0]=max(f[x][j][0]+a[i],f[s][i][0]); if(c[j]>=c[i]&&a[j]>=b[i])f[s][i][0]=max(f[s][i][0],f[x][j][1]+a[i]); if(b[j]>=c[i]&&a[j]>=b[i])f[s][i][0]=max(f[s][i][0],f[x][j][2]+a[i]); if(c[j]>=c[i]&&b[j]>=a[i])f[s][i][1]=max(f[s][i][1],f[x][j][0]+b[i]); if(c[j]>=c[i]&&a[j]>=a[i])f[s][i][1]=max(f[s][i][1],f[x][j][1]+b[i]); if(b[j]>=c[i]&&a[j]>=a[i])f[s][i][1]=max(f[s][i][1],f[x][j][2]+b[i]); if(c[j]>=b[i]&&b[j]>=a[i])f[s][i][2]=max(f[s][i][2],f[x][j][0]+c[i]); if(c[j]>=b[i]&&a[j]>=a[i])f[s][i][2]=max(f[s][i][2],f[x][j][1]+c[i]); if(b[j]>=b[i]&&a[j]>=a[i])f[s][i][2]=max(f[s][i][2],f[x][j][2]+c[i]); ans=max(ans,max(f[s][i][0],max(f[s][i][1],f[s][i][2]))); //printf("%d %d %d %d *** ",x,s,i,j); //printf("%lld %lld %lld ",f[s][i][0],f[s][i][1],f[s][i][2]); } } printf("%lld",ans); fclose(stdin);fclose(stdout); return 0; }
2.DFS
#include<bits/stdc++.h> using namespace std; template <class T> inline void readl(T &x) { x=0;bool f=0;char ch=getchar(); while(!isdigit(ch)) { f=(ch==45);ch=getchar();} while( isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?(~x+1):x;} int n,maxn=0; bool vis[1001]; struct node { int mx,h[4],x[4],y[4];}e[100]; void dfs(int H,int x,int y) { maxn=max(maxn,H); for(register int i=n;i>=1;i--) if(!vis[i]) { vis[i]=1; for(register int j=1;j<=3;j++) if(e[i].x[j]<=x&&e[i].y[j]<=y) dfs(H+e[i].h[j],e[i].x[j],e[i].y[j]); vis[i]=0; } } int main() { freopen("brick.in","r",stdin); freopen("brick.out","w",stdout); readl(n); for(register int i=1,x,y,z;i<=n;i++) { readl(x);readl(y);readl(z); e[i].mx=max(x,max(y,z)); e[i].x[1]=min(x,y);e[i].y[1]=max(x,y);e[i].h[1]=z; e[i].x[2]=min(y,z);e[i].y[2]=max(y,z);e[i].h[2]=x; e[i].x[3]=min(x,z);e[i].y[3]=max(x,z);e[i].h[3]=y; } if(n==1){printf("%d",e[1].mx);return 0;} for(int i=1;i<=n;i++) { vis[i]=1; for(int j=1;j<=3;j++) dfs(e[i].h[j],e[i].x[j],e[i].y[j]); vis[i]=0; } printf("%d",maxn); return 0; }
Problem3 同余
小X 望着草稿纸上的数列,结合自己对同余的粗浅认识,又想到了个新问题。 对于1个长度为 n 的数列 {ai},每次询问将给出1组数 l; r; p; q,小X 想知道有多少个 i 满足 l ≤ i ≤ r 且 ai ≡ q (mod p)。 小X 沉迷这个问题,因此一共进行了 m 次询问。 Input 第⼀⾏包含两个整数 n; m。 第⼆⾏包含 n 个整数,表⽰数列 faig。 接下来 m ⾏,每⾏包含四个整数 l; r; p; q,表⽰⼀次询问。 Output m ⾏,每⾏包含⼀个整数,表⽰该次询问的答案。 Example
congruence.in | congruence.out |
5 2 1 5 2 3 7 1 3 2 1 2 5 3 0 |
2 1 |
Scoring • 对于 20% 的数据, ai ≤ 1。
• 对于 60% 的数据, ai ≤ 100。
• 对于 100% 的数据, 1 ≤ m ≤ 105, 1 ≤ l ≤ r ≤ n ≤ 105, 0 ≤ q < p ≤ 10000, 0 ≤ ai ≤ 10000。
解析:
3.1 20 分做法
ai 6 1,因此转变为求区间内 0 和 1 的个数,用前缀和优化,注意 p = 1 的情况。时间复杂
度 O(n + m)。
3.2 60 分做法
同上面,用至多 101 个前缀和即可,时间复杂度 O(ai(n + m))。
3.3 100 分做法
观察到,其实要求的是某一范围内 kp + q 的个数,当 p 较大时, k 的取值范围很小。不妨
设“较大”的界限是 > K。
考虑将问题拆开来并排序,这样每个问题就变成了询问 1 ∼ r 中有多少个 kp + q。维护⼀
个哈希数组, h[i] 表示 i 有多少个;以及⼀个模数数组 g[i][j],表示模 i 为 j 有多少个。
每次指针向右移,直到移动到当前询问的位置,每移一次就将这个数分别在两个数组内标
记,复杂度总体是 O (nK)。
每次询问时,对于较小的 p 直接在 g 中查询,对于较⼤的 p 枚举 k 并在 h 中查询。
可以看出 K = √ai = 100 时最优。
代码:
1.100分
#include<bits/stdc++.h> using namespace std; template <class T> inline void readl(T &x) { x=0;bool f=0;char ch=getchar(); while(!isdigit(ch)) { f=(ch==45);ch=getchar();} while( isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?(~x+1):x;}//读入优化 int n,m; int a[100100],sum[100100],ans[100100]; struct range { int r,id,p,q,opt;}e[100100<<1];//注意开两倍的空间 int maxn=0; int cmp(range a,range b) { return a.r<b.r;} int main() { freopen("congruence.in","r",stdin); freopen("congruence.out","w",stdout); readl(n);readl(m); for(int i=1;i<=n;i++) readl(a[i]); for(int i=1,l,r,p,q;i<=m;i++) { readl(l);readl(r);readl(p);readl(q); e[i].r=l-1; e[i].id=i; e[i].p=p; e[i].q=q; e[i].opt=-1;//需要计算sum[r]-sum[l-1] e[i+m].r=r;e[i+m].id=i;e[i+m].p=p;e[i+m].q=q;e[i+m].opt=1;//将左右两边分开计算,将无序的提问变为线性的区间计算 } sort(e+1,e+1+2*m,cmp); int tail=0;//解析中的指针 for(int i=1;i<=2*m;i++) { while(tail<e[i].r) { tail++; sum[a[tail]]++;//将所有的数字出现的个数都统计一遍 maxn=max(maxn,a[tail]);//统计最大值,方便确定下文kp+q的界限 } for(int j=0;j*e[i].p+e[i].q<=maxn;j++)//解析中的kp+q ans[e[i].id]+=sum[j*e[i].p+e[i].q]*e[i].opt;//e[i].id表示询问的序号,如果opt=-1就代表左界限,乘积为负代表减去[1,l-1]的值,opt=1同理 } for(int i=1;i<=m;i++) printf("%d ",ans[i]);//输出答案 return 0; }
2.60分(重点是理解40%的前缀和写法)
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; int readl() { int x=0,fg=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fg=-fg;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*fg; } int n,m,a[101000],s[101000],v[100010][101]; int ac[101000]; int calc(int l,int r,int p,int q) { int ret=0; for(int i=l;i<=r;i++) if(a[i]%p==q%p)ret++; return ret; } int main() { freopen("congruence.in","r",stdin); freopen("congruence.out","w",stdout); n=readl();m=readl(); int bo=0; for(int i=1;i<=n;i++) { a[i]=readl();if(a[i]>1)bo=1; } int ans,l,r,p,q; if(n<=5010&&m<=5010) { for(int i=1;i<=m;i++) { l=readl();r=readl();p=readl();q=readl(); printf("%d ",calc(l,r,p,q)); } return 0; } if(bo==0) { for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]; for(int i=1;i<=m;i++) { l=readl();r=readl();p=readl();q=readl(); if(p==1){ans=r-l+1;printf("%d ",ans);continue;} if(q%p==0)ans=r-l+1-s[r]+s[l-1]; if(q%p==1)ans=s[r]-s[l-1]; if(q%p>1)ans=0; printf("%d ",ans); } return 0; } for(int i=1;i<=n;i++)//重点 for(int j=0;j<=100;j++) { v[i][j]=v[i-1][j]; if(a[i]==j)v[i][j]++; } for(int i=1;i<=m;i++) { l=readl();r=readl();p=readl();q=readl();ans=0; if(p==1){ans=r-l+1;printf("%d ",ans);continue;} ac[0]=0; for(int i=0;i<=100;i++) if(i%p==q%p)ac[++ac[0]]=i; for(int i=1;i<=ac[0];i++) ans=ans+v[r][ac[i]]-v[l-1][ac[i]]; printf("%d ",ans); } fclose(stdin);fclose(stdout); return 0; }