长沙学院校赛
首先看到n=1e14,可以想到肯定是根号n的算法
然后阅读题目发现是是求每个数的因子之和,显然我们可以枚举因子取计算贡献(就是算出拥有该因数的数的个数再乘以该因子大小)
我们又发现很多因子在1~n中拥有它们的个数是相同的
于是想到了分块
可能更正常的思路:
直接枚举每个数的每个因子O(n*根号n)-->枚举每个因子算贡献O(n)-->发现可以用分块优化O(根号n)
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; const int mod=1000000007; LL inv2; LL qmi(LL a,LL k,LL p){ LL res=1%p; while(k){ if(k&1)res=res*a%p; a=a*a%p; k>>=1; } return res; } LL sum(LL l,LL r){ LL a=(l+r)%mod; LL b=(r-l+1)%mod; return a*b%mod*inv2%mod; } int main(){ ios::sync_with_stdio(false); cin.tie(0); inv2=qmi(2,mod-2,mod); LL n; cin>>n; LL res=0; int cnt=0; for(LL i=1;i<=n;i++){ LL k=n/i; LL r=n/k; res=(res+k%mod*sum(i,r)%mod)%mod; i=r; cnt++; } cout<<res<<endl; return 0; }
感觉搜索->dp->贪心是一个不错的思考流程
我们要分别考虑每个"?"位置放哪个字母,思考暴力搜索可不可行,时间复杂度为O(26^(1e3)),显然完全不行,其实看到取模基本就给搜索判死刑了
然后思考dp,dp的关键就是状态表示和状态转移
从dp->贪心的关键就是看每一步是否能够直接判断哪个选择更好,显然这里无法判断,放哪个字母都是可以的,所以这里使用dp
没看出dp真是抱歉,现在我的dp实际上水平很低,但这题的状态还是能够写出来
好吧hh,实际上尝试了一下,直接就写出来了。。
状态表示根据需要加维度就行,状态转移是根据下一个位置放什么字母来进行分类
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; const int N=1e3+10,mod=1000000007; LL f[N][10][26][2]; string s; int n; int main(){ ios::sync_with_stdio(false); cin.tie(0); cin>>s; n=s.size(); //初始化卡了一下 if(s[0]!='?')f[0][0][s[0]-'a'][0]=1; else{ for(int c=0;c<26;c++) f[0][0][c][0]=1; } //这里to的写法更好,所以使用to写法 for(int i=0;i<n-1;i++){ for(int j=0;j<10;j++){ for(int k=0;k<26;k++){ for(int l=0;l<2;l++){ if(s[i+1]!='?'){ int c=s[i+1]-'a'; if(c==k+1){ int t=0; if(j+1==9)t=1; f[i+1][min(j+1,9)][c][max(l,t)]=(f[i+1][min(j+1,9)][c][max(l,t)]+f[i][j][k][l])%mod; }else{ f[i+1][0][c][l]=(f[i+1][0][c][l]+f[i][j][k][l])%mod; } continue; } for(int c=0;c<26;c++){ if(c==k+1){ int t=0; if(j+1==9)t=1; f[i+1][min(j+1,9)][c][max(l,t)]=(f[i+1][min(j+1,9)][c][max(l,t)]+f[i][j][k][l])%mod; }else{ f[i+1][0][c][l]=(f[i+1][0][c][l]+f[i][j][k][l])%mod; } } } } } } LL res=0; //将所有符合条件的方案数全部加起来 for(int j=0;j<10;j++) for(int k=0;k<26;k++) res=(res+f[n-1][j][k][1])%mod; cout<<res<<endl; return 0; }
其实我觉得这题可以作为一个构造题
先说使得数组最多不同个数的构造方法:按照每个数出现的次数从大到小排序,如5555533112,然后整个数组向前移动知道5被其他数完全替代
也就是3位于第一位时
其实这种移动数组也算是一个构造的典了吧,经常用来改变一个数组使得与之前不同
首先这种构造得以正确的一个原因是题目只要求每个位置对应的数与之前不同即可,所以我们用任意一个数来替换这个位置都行
并且可以发现用任意的数来进行替换并不会造成负面影响(我们可以在这种构造的数组的基础上交换若干数达成所有的正确的构造数组)
我们发现当最多的出现次数max_cnt<=n-max_cnt,在这种构造方式下,所有的位置的数都可以与之前不同
max_cnt>n-max_cnt时,出现次数最大的那个数会与自己出现重叠,重叠的长度为2*max_cnt-n,其他位置都不同,所以ans=n-(2*max_cnt-n)=2*(n-max_cnt)
并且通过自己瞎试可以发现没有办法使得ans更大
于是现在我们只需要分类输出结果即可
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; const int N=1e5+10; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; vector<int>cnt(N); int res=0; for(int i=0;i<n;i++){ int x; cin>>x; cnt[x]++; res=max(res,cnt[x]); } if(res<=n-res)cout<<n<<endl; else cout<<2*(n-res)<<endl; return 0; }
题意为从起点经过若干固定点到终点的最短距离,感觉这题比acwing上的拆点题更加典
对于行走的这个人,我们用二进制来表示其搜集到的卡片情况,预处理整张图的key(每个点拥有的卡片情况),然后直接跑bfs即可
此处注意是拆点,每个不同的点的不同状态也算不同的点,需要入队列,同时拆点的bfs也满足最先到达就是最短路
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; const int N=105,M=1<<10; char g[N][N]; int d[N][N][M]; int key[N][N]; int sx,sy,ex,ey; int n,m; int dx[]={-1,0,1,0},dy[]={0,1,0,-1}; int bfs(){ memset(d,0x3f,sizeof d); d[sx][sy][0]=0; queue<array<int,3>>q; q.push({sx,sy,0}); while(q.size()){ auto [x,y,state]=q.front(); q.pop(); for(int i=0;i<4;i++){ int a=x+dx[i],b=y+dy[i]; if(a<0||a>=n||b<0||b>=m)continue; if(g[a][b]=='#')continue; int t=state|key[a][b]; if(d[a][b][t]!=INF)continue; d[a][b][t]=d[x][y][state]+1; q.push({a,b,t}); } } if(d[ex][ey][M-1]==INF)return -1; return d[ex][ey][M-1]; } int main(){ ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m; for(int i=0;i<n;i++)cin>>g[i]; string t="JOKERjoker"; for(int i=0;i<n;i++) for(int j=0;j<m;j++){ if(g[i][j]=='(')sx=i,sy=j; if(g[i][j]==')')ex=i,ey=j; for(int k=0;k<10;k++){ if(g[i][j]==t[k])key[i][j]=1<<k; } } cout<<bfs()<<endl; return 0; }
还有做法二
我们可以预处理“(JOKERjoker”,这个字符串中每个点到其他点的最短距离,也就是以每个点跑一遍bfs
然后全排列枚举走的顺序,将距离和加起来,取最小值即可
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; const int N=12,M=110; vector<string>g; int d[N][M][M]; PII p[N]; int cnt; int n,m; int dx[]={-1,0,1,0},dy[]={0,1,0,-1}; void bfs(int k,int d[][M]){ auto [sx,sy]=p[k]; d[sx][sy]=0; queue<PII>q; q.emplace(sx,sy); while(q.size()){ auto [x,y]=q.front(); q.pop(); for(int i=0;i<4;i++){ int a=x+dx[i],b=y+dy[i]; if(a<0||a>=n||b<0||b>=m)continue; if(g[a][b]=='#')continue; if(d[a][b]!=INF)continue; d[a][b]=d[x][y]+1; q.emplace(a,b); } } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m; memset(d,0x3f,sizeof d); string tmp="(JOKERjoker)"; g.resize(n); for(auto &v:g)cin>>v; for(int i=0;i<n;i++) for(int j=0;j<m;j++) for(int k=0;k<12;k++){ if(g[i][j]==tmp[k])p[k]={i,j}; } for(int i=0;i<11;i++)bfs(i,d[i]); vector<int>id(12); for(int i=1;i<11;i++)id[i]=i; id[11]=11; LL res=INF; do{ // for(int i=0;i<id.size();i++)cout<<id[i]<<' '; // cout<<endl; LL t=0; for(int i=0;i<=10;i++){ auto [x,y]=p[id[i+1]]; t+=d[id[i]][x][y]; } res=min(res,t); }while(next_permutation(id.begin()+1,id.end()-1)); if(res==INF)res=-1; cout<<res<<endl; return 0; }
很容易看出来排序二分就行
关键在于按照什么依据去二分,接下来的思考就比较典了,考虑2项i,j,比较以i,j的顺序和j,i的顺序得到的舒适度
通过对式子的变形可以得到当gi*vj>gj*vi时,i,j的排列方式更好
这意味着当i,j2项都要选时,按照这样的次序一定更好,同时可以发现这样对于其他项无影响,并且所有项都需要使用
所以我们可以放心用该标准进行排序(写的时候还踩了个坑,cmp里面必须严格偏序,否则会段错误)
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; LL n,m; bool cmp(PII a,PII b){ auto [g1,v1]=a; auto [g2,v2]=b; return g1*v2>g2*v1; } bool check(LL x,vector<PII>&q){ LL res=0; for(auto &[g,v]:q){ res+=g*(x-v); //防止爆longlong,提前退出 if(res>=m)return true; x-=v; } return res>=m; } int main(){ ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m; vector<PII>q(n); LL sum=0; for(auto &[g,v]:q)cin>>g>>v,sum+=v; sort(q.begin(),q.end(),cmp); LL l=sum,r=1e13; while(l<r){ LL mid=l+r>>1; if(check(mid,q))r=mid; else l=mid+1; } cout<<l<<endl; return 0; }
签到题,模拟即可
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; int main(){ ios::sync_with_stdio(false); cin.tie(0); LL n; cin>>n; vector<int>eight; while(n){ eight.push_back(n%8); n/=8; } reverse(eight.begin(),eight.end()); eight.push_back(1); swap(eight[eight.size()-1],eight[eight.size()-2]); LL res=0; for(auto &v:eight){ res=res*8+v; } cout<<res<<endl; return 0; }
签到题
#include<bits/stdc++.h> using namespace std; #define endl '\n' typedef long long LL; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; vector<int>a,b; while(n){ a.push_back(n&1); n>>=1; } b=a; reverse(a.begin(),a.end()); if(a==b)cout<<"Yes"<<endl; else cout<<"No"<<endl; return 0; }