A - 高橋君とカード / Tak and Cards (DP)
题目大意:
有 \(n\) 个数字,要求取出一些数字,使得它们的平均数恰好为 \(x\) ,问有几种取法。
大致思路:
只要将每一个数字减掉 \(x\) ,那么问题就变成在 \(n\) 个数字中选取一些数字使得和为 \(0\) 的方案数,是一个经典的 \(dp\) 问题,不过要注意细节问题
代码:
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=5010; const int Y=2500; ll dp[N][N]; int n,x; int a[N]; int main() { //freopen("H:\\c++1\\in.txt","r",stdin); //freopen("H:\\c++1\\out.txt","w",stdout); scanf("%d%d",&n,&x); dp[0][Y]=1; for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i]-=x; for(int i=1;i<=n;i++){ for(int j=-Y;j<=Y;j++){ dp[i][j+Y+a[i]]+=dp[i-1][j+Y]; dp[i][j+Y]+=dp[i-1][j+Y]; } } printf("%lld\n",dp[n][Y]-1); //记得减一 return 0; }
B - 桁和 / Digit Sum (思维)
题目大意:
定义 \(f(n,b)\) 为 \(n\) 在 \(b\) 进制下各位数之和,现在给定 \(,n,s\) ,为求得最小的 \(b\) 使得 \(f(n,b)=s\)
\((n,s<=1e11)\)
大致思路:
这题很巧妙,首先如果答案 \(b\) 小于 \(1e6\) ,那么直接暴力枚举即可,若 \(b>1e6\) ,那么由于 \(n<=1e11\) ,那么 \(n\) 必然可以写成 \(n=kb+r\) ,且 \(s=k+r\) ,的形式, \(n-s=k(b-1)\) ,只要枚举 \(n-s\) 的因数即可解决,思维好题。
代码:
#include<bits/stdc++.h> #define ll long long using namespace std; ll n,s; bool check(ll x){ if(n/x>=x)return 0; if(x<2)return 0; ll k=n/x;ll r=n%x; if(s==k+r)return 1; return 0; } ll js(ll x,ll b){ ll ans=0; while(x){ ans+=x%b; x/=b; } return ans; } int main() { //freopen("H:\\c++1\\in.txt","r",stdin); //freopen("H:\\c++1\\out.txt","w",stdout); cin>>n>>s; ll b=1e18; if(n==s)b=n+1; if(n>s){ ll temp=n-s; for(ll t=1;t<=sqrt(temp);t++){ if(temp%t==0){ ll x=t+1,y=temp/t+1; if(check(x))b=min(b,x); if(check(y))b=min(b,y); } } } for(ll t=2;t<=1000000;t++){ ll cnt=js(n,t); if(cnt==s){ b=min(b,t); break; } } if(b==1e18)printf("-1\n"); else printf("%lld\n",b); return 0; }
E - 高橋君とホテル / Tak and Hotels (倍增)
题目大意:
水平轴上有 \(n\) 个点,每次跳不能超过 \(L\) ,每次必须跳在点上, \(Q\) 次询问,每次询问 \(,x,y\) ,表示从 \(x\) 到 \(y\) 最少需要跳几下。
大致思路:
感觉这题比较常见,是倍增的经典套路,用二分处理出 \(dp[i][0]\) ,跑一下预处理,然后就每次询问log查询就行了。
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e5+10; int dp[N][23]; int n,L; int a[N]; int q,x,y; int ef(int id,int v){ int l=id,r=n,ans=id; while(l<=r){ int mid=(l+r)/2; if(a[mid]<=v)ans=mid,l=mid+1; else r=mid-1; } return ans; } void cl(){ // for(int i=1;i<=n;i++)dp[i][0]=ef(i,a[i]+L); for(int j=1;j<23;j++){ for(int i=1;i<=n;i++){ dp[i][j]=dp[dp[i][j-1]][j-1]; } } } int js(int x,int y){ int ans=0,pos=x; for(int i=22;i>=0;i--){ if(dp[pos][i]<y){ pos=dp[pos][i]; ans+=(1<<i); } } ans++; return ans; } int main() { //freopen("H:\\c++1\\in.txt","r",stdin); //freopen("H:\\c++1\\out.txt","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); scanf("%d",&L); scanf("%d",&q); cl(); while(q--){ scanf("%d%d",&x,&y); if(x>y)swap(x,y); printf("%d\n",js(x,y)); } return 0; }
D - 最良表現 / Best Representation (KMP)
题意大意:
定义一个字符串合法:当该字符串没有循环节存在,给出 \(string s\) ,令 \(F=(f_1,f_2..f_m)\) 满足 \(f_i\) 为 \(s\) 的某一部分. \(f_1,f_2,..f_m\) 连起来为 \(s\) .并且任意 \(f_i\) 为合法
\(|s|<=5e5\),求出 \(F\) 表示中最小的 \(m\) .并求出最小 \(m\) 的方案数?
大致思路:
这题感觉不难,只是由于是最后一题心理上有些畏惧,可惜了,首先先求出字符串的循环节,如果不存在循环节那么答案就是 \(1\ 1\) ,特判每一个字母都相同的情况答案为 \(len\ 1\) ,比较显然,那么我们可以确定剩下的字符串必然可以分成两个好串,那么我们只要枚举断点用 \(kmp\) 判断前面的字符串和后面的字符串是否为好串即可,代码实现也比较简单。
代码:
#include<bits/stdc++.h> using namespace std; const int N=5e5+10; char s[N]; char s1[N]; int nxt[N],nxt1[N];//next数组 void kmp(char *t,int *nxt){//t为去匹配,s为被匹配 int i,j; int len1=strlen(t+1); nxt[0]=nxt[1]=0; for(i=2,j=0;i<=len1;i++){ while(j&&t[j+1]!=t[i])j=nxt[j]; if(t[j+1]==t[i])++j; nxt[i]=j; } } bool check(int x,int len){ int f1,f2; int l1=x-nxt[x],l2=len-x-(nxt1[len-x]); //if(x==2)cout<<l1<<" "<<l2<<endl; if(x==1||l1==x||x%l1!=0)f1=1; else f1=0; if(len-x==1||l2==len-x||(len-x)%l2!=0)f2=1; else f2=0; return (f1&&f2); } int main() { //freopen("H:\\c++1\\in.txt","r",stdin); //freopen("H:\\c++1\\out.txt","w",stdout); scanf("%s",s+1); int len=strlen(s+1); for(int i=1;i<=len;i++){ s1[i]=s[len-i+1]; } kmp(s,nxt);kmp(s1,nxt1); int temp=len-nxt[len]; if(temp==1){ printf("%d\n%d\n",len,1);return 0; } if(temp!=len&&len%temp==0){ int ans=0; for(int i=1;i<len;i++){ if(check(i,len))ans++; } printf("2\n%d\n",ans); } else{ printf("1\n1\n"); } return 0; }