Day1 T1:
链接:https://www.luogu.com.cn/problem/T149958
用字符串读入的水题。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 using namespace std; 5 char s[1000005]; 6 int cnt; 7 int main(){ 8 freopen("magic.in","r",stdin); 9 freopen("magic.out","w",stdout); 10 scanf("%s",s); 11 int len=strlen(s); 12 for(int i=0;i<len;i++){ 13 if(s[i]=='0'||s[i]=='1'||s[i]=='2'||s[i]=='9'){ 14 cnt++; 15 } 16 } 17 if(cnt==len) printf("Yes "); 18 else printf("No "); 19 return 0; 20 }
Day1 T2:
链接:https://www.luogu.com.cn/problem/U132839
Day1 T3:
链接:https://www.luogu.com.cn/problem/U132840
Day1 T4:
链接:https://www.luogu.com.cn/problem/T149959
Day2 T1:
链接:https://www.luogu.com.cn/problem/T149963
明显的贪心,但这道题自己在考场上时考虑过于简单,只是a从小到大排序,b从小到大排序,然后for i=1...n,sum+=a[i]+b[i+1]。
但是忽略了一些特殊情况:如a=0.1 b=0.1,100。
此时应该让 a[1]和b[1]相乘,然后再加上b[2]才为最大值。
那么可以直接在考虑的时候在a[]数组中直接添加一个元素a[]=1,这样不会对答案产生任何影响,然后对a[]和b[]分别排序,sum+=a[i]*b[i]即可。
这是一种比较重要且常见的思想。
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; const int N=100005; int n; double a[N],b[N],sum; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lf",&a[i]); a[n+1]=1; for(int i=1;i<=n+1;i++) scanf("%lf",&b[i]); sort(a+1,a+n+2); sort(b+1,b+n+2); for(int i=1;i<=n+1;i++) sum+=a[i]*b[i]; printf("%lf",sum); return 0; }
Day2 T2:
链接:https://www.luogu.com.cn/problem/T149734
根据唯一分解定理,可以将每个正整数n分解成$p_1^{q_1} imes p_2^{q_2} imes p_3^{q_3}...$,然后对于每个数统计质因数2和5的个数。(这里自己想到了)
下一步便要找出0数量最多的那m个数。如果用暴力枚举的话只能的30pts。
考虑dp:
设计:
设dp[i][j][k]表示从前i个数中选j个,有k个5时2的个数最多是多少。(注意最后一维要是5的个数。2的个数太多,枚举时会T)
不难发现,dp[i][j][k]总是从dp[i-1][][]中转移过来的,所以可以压掉第一维。直接dp[j][k]。
转移:
初始f[0][0]=0,其他赋为0xc0。
f[j][k]=max(f[j][k],f[j-1][k-s[i]]+t[i])。其中s[i]表示i中5的个数,t[i]表示i中2的个数。
从不选i(f[j][k])和选i(f[j-1][k-s[i]]+t[i])转移。
枚举5的个数k,最后答案为max(min(k,f[n][m][k])。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 typedef long long ll; 7 const int N=205; 8 int n,m,ans; 9 int cnt[N][2]; 10 ll a; 11 int f[N][N*30]; 12 //1<<60 13 //f[i][j][k] 从前i个数中选j个,其中2的个数为k 5的个数 14 int main(){ 15 scanf("%d%d",&n,&m); 16 for(int i=1;i<=n;i++){ 17 scanf("%lld",&a); 18 while(a%2==0){ 19 cnt[i][0]++; 20 a/=2; 21 } 22 while(a%5==0){ 23 cnt[i][1]++; 24 a/=5; 25 } 26 } 27 memset(f,0xc0,sizeof(f)); 28 f[0][0]=0; 29 for(int i=1;i<=n;i++){ 30 for(int j=min(m,i);j>=1;j--){ 31 for(int k=cnt[i][1];k<=30*i;k++) 32 f[j][k]=max(f[j-1][k-cnt[i][1]]+cnt[i][0],f[j][k]); 33 } 34 } 35 for(int i=1;i<=30*n;i++){ 36 int t=min(i,f[m][i]); 37 ans=max(ans,t); 38 } 39 printf("%d",ans); 40 return 0; 41 }
Day2 T3:
链接:https://www.luogu.com.cn/problem/T149741
Day2 T4:
链接:https://www.luogu.com.cn/problem/T149969
Day3 T1:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 const int mod=10007; 5 const int N=1005; 6 int n,m,sum[N],a[N]; 7 void init(){ 8 for(int i=1;i<=1000;i++){ 9 int t=1; 10 for(int j=0;j<=n;j++){ 11 sum[i]+=a[j]*t; 12 sum[i]%=mod; 13 t*=i; 14 t%=mod; 15 } 16 } 17 } 18 int main(){ 19 scanf("%d%d",&n,&m); 20 for(int i=n;i>=0;i--) scanf("%d",&a[i]); 21 init(); 22 for(int i=1;i<=m;i++){ 23 int c; 24 scanf("%d",&c); 25 printf("%d ",sum[c]); 26 } 27 return 0; 28 }
Day3 T2:
链接:https://www.luogu.com.cn/problem/T149876
根据唯一分解定理,可以得到结论:a和b的公约数数量=gcd(a,b)的约数数量。
∵c|a,c|b,$s_i<=min(q_i,r_i)$
$a=p_1^{q_1} imes p_2^{q_2} imes p_3^{q_3} imes ... imes p_n^{q_n}$
$b=p_1^{r_1} imes p_2^{r_2} imes p_3^{r_3} imes ... imes p_n^{r_n}$
∴$c=p_1^{min(r_1,q_1)} imes p_2^{min(r_2,q_2)} imes p_3^{min(r_3,q_3)} imes ... imes p_n^{min(r_n,q_n)}$
设$s_i=min(q_i,r_i)$,则数量为$(s_1+1) imes (s_2+1) imes ... imes(s_i+1)... imes(s_n+1)$
所以这道题可以先预处理出[1,100000]中每个数的约数的个数:
for(int i=1;i<=100000;i++){ for(int j=i;j<=100000;j+=i){ f[j]++; } }
注意这段代码的时间复杂度为logn级别的。
然后每输入a,b,求出它们的gcd,O(1)输出答案。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 const int N=100005; 5 int T; 6 int f[N]; 7 int gcd(int a,int b){ 8 if(b==0) return a; 9 return gcd(b,a%b); 10 } 11 void init(){ 12 for(int i=1;i<=100000;i++){ 13 for(int j=i;j<=100000;j+=i){ 14 f[j]++; 15 } 16 } 17 }//1+1/2+1/3+..+1/n是logn级别的 18 int main(){ 19 scanf("%d",&T); 20 init(); 21 while(T--){ 22 int a,b; 23 scanf("%d%d",&a,&b); 24 int d=gcd(a,b); 25 printf("%d ",f[d]); 26 } 27 return 0; 28 }
Day3 T3:
链接:https://www.luogu.com.cn/problem/T149880
找规律:
通过手推数据(严格证明)可以得到规律:
如果最大值大于其他所有数的和的话,那么先手一定会赢(让先手一直选最大的那个);
如果不是的话,所有数和为偶数,则后手赢;否则先手赢。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 int n,maxx,T,sum; 6 int main(){ 7 scanf("%d",&T); 8 while(T--){ 9 sum=0; 10 maxx=0; 11 scanf("%d",&n); 12 for(int i=1;i<=n;i++){ 13 int a; 14 scanf("%d",&a); 15 sum+=a; 16 maxx=max(a,maxx); 17 } 18 if(maxx>(sum-maxx)){printf("Alice ");continue;} 19 if(sum&1) printf("Alice "); 20 else printf("Bob "); 21 } 22 return 0; 23 }
Day3 T4:
链接:https://www.luogu.com.cn/problem/U132994
这道题自己的思路被限制到了,在考场上一直在求解n元一次方程,并没有写成递推的形式。然而求解n元一次方程代码量大,容易把自己绕进去,所以以后尽量避开n元一次方程组的求解。
这道题可以写成递推形式,从小到大递推。根据题中的信息,可以列出递推式:
$A_{i+1}=-2 imes A_i+A_{i-1}+2 imes d$
然后设$A_2=x$,那么通过递推,那么任意$A_i$可以写成ax+b的形式,则用s[i]存a,t[i]存b。
每次递推,s[i+1]=-2*s[i]+s[i-1],t[i+1]=-2*t[i]+t[i-1]+2*d。
那么A[n]也可以用ax+b表示出来,而A[n]也是已知的。从而可以解出x。A[m]即为s[m]*x+t[m]。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 const int N=105; 5 int n,m; 6 double d,a[N]; 7 double s[N],t[N]; 8 int main(){ 9 scanf("%d%d",&n,&m); 10 scanf("%lf%lf%lf",&d,&a[1],&a[n]); 11 t[1]=a[1]; s[2]=1; 12 for(int i=3;i<=n;i++){ 13 s[i]=-2*s[i-1]+s[i-2]; 14 t[i]=-2*t[i-1]+t[i-2]+2*d; 15 } 16 double x=(a[n]-t[n])/s[n]; 17 printf("%.3lf",s[m]*x+t[m]); 18 return 0; 19 }
Day4 T1:
链接:https://www.luogu.com.cn/problem/T150020
法一:一个二次函数求最值的题目,直接分类讨论,比较建议。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 using namespace std; 5 int a,b,c; 6 int main(){ 7 freopen("game.in","r",stdin); 8 freopen("game.out","w",stdout); 9 scanf("%d%d%d",&a,&b,&c); 10 if(a==0){ 11 if(b>0) printf("12"); 12 else printf("0"); 13 return 0; 14 } 15 double t=b*1.0/(-2*a); 16 if(a>0){ 17 if(t<=0) printf("12"); 18 else if(t>0&&t<12){ 19 double l=t; 20 double r=12-t; 21 if(l>=r) printf("0"); 22 else printf("12"); 23 } 24 else printf("0"); 25 } 26 else{ 27 if(t>0&&t<12){ 28 if(t-floor(t)<=ceil(t)-t) printf("%d",(int)floor(t)); 29 else printf("%d",(int)ceil(t)); 30 } 31 else if(t<=0) printf("0"); 32 else printf("12"); 33 } 34 return 0; 35 }
法二:暴力枚举,从0到12,注意都要开ll,尤其注意在赋值之前的计算时也要转成,否则会炸。
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 typedef long long ll; 5 ll a,b,c,id; 6 ll maxx=-1e16; 7 int main(){ 8 scanf("%lld%lld%lld",&a,&b,&c); 9 for(ll i=0;i<=12;i++){ 10 ll t=a*i*i+b*i+c; 11 if(maxx<t) maxx=t,id=i; 12 } 13 printf("%lld ",id); 14 return 0; 15 }
Day4 T2:
链接:https://www.luogu.com.cn/problem/T150022
法一:
直接暴力从L~R枚举,因为L很大,R很大,但R-L很小,而判断一个数是不是质数只需要$sqrt(n)$的复杂度,这样从L到R暴力判断每个是不是素数只需要$O((R-L) imes sqrt(R))$的时间复杂度。这个是可以接受的。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 using namespace std; 5 typedef long long ll; 6 int l,r; 7 int flag; 8 ll sum; 9 int main(){ 10 scanf("%d%d",&l,&r); 11 for(int i=l;i<=r;i++){ 12 int t=sqrt(i); 13 flag=0; 14 for(int j=2;j<=t;j++){ 15 if(!(i%j)) {flag=1;break;} 16 } 17 if(!flag) sum+=i; 18 } 19 printf("%lld ",sum); 20 return 0; 21 }
法二:
根据欧拉筛的思想,每个数只会被它的最小质因数给筛掉。而R范围中的数,它的最小质因数只会出现2~$sqrt(n)$的范围内。所以可以先预处理出2~$sqrt(n)$中的素数表,然后用这些素数来筛[L,R]区间内的素数。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 typedef long long ll; 5 const int N=1000005; 6 int cnt; 7 int prime[N]; 8 bool vis[N],vis1[N]; 9 int L,R; 10 ll sum; 11 void is_prime(){ 12 for(int i=2;i*i<=R;i++){ 13 if(!vis[i]){ 14 for(int j=i*2;j*j<=R;j+=i){ 15 vis[j]=1;//筛[2,sqrt(R)] 16 } 17 for(int j=max(2,(L+i-1)/i)*i;j<=R;j+=i){ 18 //(a+i-1)/i为[a,b)区间内第一个数至少为i的多少倍 19 vis1[j-L]=1;//筛[a,b] 20 } 21 } 22 } 23 } 24 int main(){ 25 freopen("prime.in","r",stdin); 26 freopen("prime.out","w",stdout); 27 scanf("%d%d",&L,&R); 28 is_prime(); 29 for(int i=L;i<=R;i++){ 30 if(!vis1[i-L]) sum+=i; 31 } 32 printf("%lld",sum); 33 return 0; 34 }
Day4 T3:
链接:https://www.luogu.com.cn/problem/T150024
Day4 T4:
链接:https://www.luogu.com.cn/problem/T150031
首先不考虑边权,那么Alice和Bob都应该选当前可选中的最大的。对于每一个边权,如果一个人把两边的点都选了,他才能得到这个边权,但两个人分别选两端的两个点,那么这条边的边权不属于任何人。而恰好这道题最后只考虑Alice的得分减去Bob的得分。
所以这道题完全可以将每条边的边权分成两份,分给这条边两端的点。然后让两个人贪心地从大到小选。
但是这样可能会有小数等的情况。所以可以直接将点权*=2,然后两端的点权+=边权。最后答案直接/=2即可,是等效的。
AC代码:
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 const int N=10005; 7 int n,m; 8 ll sum1,sum2; 9 ll w[N]; 10 bool cmp(ll a,ll b){ 11 return a>b; 12 } 13 int main(){ 14 freopen("play.in","r",stdin); 15 freopen("play.out","w",stdout); 16 scanf("%d%d",&n,&m); 17 for(int i=1;i<=n;i++) {scanf("%lld",&w[i]); w[i]*=2;} 18 for(int i=1;i<=m;i++){ 19 int a,b,c; 20 scanf("%d%d%d",&a,&b,&c); 21 w[a]+=c; w[b]+=c; 22 } 23 sort(w+1,w+n+1,cmp); 24 for(int i=1;i<=n;i++){ 25 if(i&1) sum1+=w[i]; 26 else sum2+=w[i]; 27 } 28 printf("%lld ",(sum1-sum2)/2); 29 return 0; 30 }
Day5 T1:
链接:https://www.luogu.com.cn/problem/T150188
很明显这是一个贪心问题。
可以有两种(或更多的贪心写法):
自己在考场上写的是一个类似调整法贪心:
按它们的左端点排序。假设上一个区间是选了的,用pre记录它的右端点。我们在枚举下一个区间的时候,如果它的pre>a[i].l,也就是当前区间与它重合,那么就在pre这个区间和当前a[i]这个区间两者之间做一个选择:选择一个右端点更小的,它一定更优。并用pre记录下来。然而这些操作都不会对cnt产生影响。如果pre<=a[i].l,那么就先把a[i]这个区间选上,之后再做调整。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int N=1e6+5; 6 int n,vis[N],cnt; 7 int pre; 8 int T; 9 struct node{ 10 int l,r; 11 }a[N]; 12 bool cmp(node a,node b){ 13 return a.l<b.l; 14 } 15 int main(){ 16 freopen("count.in","r",stdin); 17 freopen("count.out","w",stdout); 18 scanf("%d",&n); 19 for(int i=1;i<=n;i++) scanf("%d%d",&a[i].l,&a[i].r); 20 sort(a+1,a+n+1,cmp); 21 for(int i=1;i<=n;i++){ 22 if(pre>a[i].l) pre=min(a[i].r,pre); 23 else {cnt++; pre=a[i].r;} 24 } 25 printf("%d",cnt); 26 return 0; 27 }
也可以直接用普通贪心做:
把它们直接按右端点排序,从左往右扫,如果当前a[i].l>=pre,即能选,则就选。选当前这个一定会比选它的下一个更优:
因为不选这一个,所省下来的空间最多留给下一个区间,而下一个区间的右端点更靠右。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int N=1000005; 6 int n,pre,cnt; 7 struct node{ 8 int l,r; 9 }q[N]; 10 bool cmp(node a,node b){ 11 return a.r<b.r; 12 } 13 int main(){ 14 scanf("%d",&n); 15 for(int i=1;i<=n;i++){ 16 scanf("%d%d",&q[i].l,&q[i].r); 17 } 18 sort(q+1,q+n+1,cmp); 19 for(int i=1;i<=n;i++){ 20 if(pre<=q[i].l) {cnt++; pre=q[i].r;} 21 } 22 printf("%d",cnt); 23 return 0; 24 }
Day5 T2:
链接:https://www.luogu.com.cn/problem/T150189
首先根据贪心的思想,按照p从大到小排序。然后进行DP:
设dp[i][j]表示从排序后前i头猪中选j头的最大获利。每次转移考虑第i头猪选不选。
如果选,则dp[i][j]=dp[i-1][j-1]+当前时刻第i头的价值。如果不选,则dp[i][j]=dp[i-1][j],取max。
注意在DP过程中取max。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int N=1005; 6 int n,k,ans; 7 int f[N][N]; 8 struct node{ 9 int p,a; 10 }q[N]; 11 bool cmp(node a,node b){ 12 return a.p>b.p; 13 } 14 int main(){ 15 scanf("%d%d",&n,&k); 16 for(int i=1;i<=n;i++) scanf("%d",&q[i].a); 17 for(int i=1;i<=n;i++) scanf("%d",&q[i].p); 18 sort(q+1,q+n+1,cmp); 19 for(int i=1;i<=n;i++){ 20 for(int j=k;j>=1;j--){ 21 f[i][j]=max(f[i][j],max(f[i-1][j],f[i-1][j-1]+q[i].a-q[i].p*(j-1))); 22 ans=max(f[i][j],ans); 23 } 24 } 25 printf("%d",ans); 26 return 0; 27 }
不难发现dp[i][]都是由dp[i-1][]转移而来的,所以可以压掉第一维。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int N=1005; 6 int n,k,ans; 7 int f[N]; 8 struct node{ 9 int p,a; 10 }q[N]; 11 bool cmp(node a,node b){ 12 return a.p>b.p; 13 } 14 int main(){ 15 scanf("%d%d",&n,&k); 16 for(int i=1;i<=n;i++) scanf("%d",&q[i].a); 17 for(int i=1;i<=n;i++) scanf("%d",&q[i].p); 18 sort(q+1,q+n+1,cmp); 19 for(int i=1;i<=n;i++){ 20 for(int j=k;j>=1;j--){ 21 f[j]=max(f[j],f[j-1]+q[i].a-q[i].p*(j-1)); 22 ans=max(f[j],ans); 23 } 24 } 25 printf("%d",ans); 26 return 0; 27 }
Day5 T3:
链接:https://www.luogu.com.cn/problem/T150191
题中有一个比较好的提示,当n=5时,答案为89。而在斐波那契数列中F[10]恰好为89。那么可以大胆猜想答案即为F[2*n]。
严格:
首先题目要求的可以写成:$Sigma^n_{i=0}{C_n^i} imes F_i$
证明可以通过F数列的通项公式和二项式定理可以进行证明:
F数列通项公式:$F_n = frac {1}{sqrt{5}} imes ((frac{1+sqrt{5}}{2})^{n+1}-(frac{1-sqrt{5}}{2})^{n+1})$
二项式定理:$(x+y)^n = Sigma_{i=0}^n C^i_nx^iy^{n-i}$
证明:
$ Sigma^n_{i=0}{C_n^i} imes F_n$
=$Sigma^n_{i=0}{C_n^i}*frac {1}{sqrt{5}} imes [(frac{1+sqrt{5}}{2})^{n+1}-(frac{1-sqrt{5}}{2})^{n+1}]$
=$frac{1}{sqrt{5}}(Sigma^n_{i=0}{C_n^i}[(frac{1+sqrt{5}}{2})^{i+1}-(frac{1-sqrt{5}}{2})^{i+1})]$
=$frac{1}{sqrt{5}}[(frac{1+sqrt{5}}{2})(frac{3+sqrt{5}}{2})^n)-(frac{1-sqrt{5}}{2})(frac{3-sqrt{5}}{2})^n]$
=$frac{1}{sqrt{5}}[(frac{1+sqrt{5}}{2})^{2n+1}-(frac{1-sqrt{5}}{2})^{2n+1}]$
=$F_{2n}$
Day5 T4:
Day6 T1:
链接:https://www.luogu.com.cn/problem/T150380
Day6 T2:
链接:https://www.luogu.com.cn/problem/T150382
Day6 T3:
链接:https://www.luogu.com.cn/problem/T150384
Day6 T4: