报数
1.禁止报的数的生成规则与埃氏筛法类似,考虑用筛法预处理可以报出的数字列表和不可报出的数字,从而 O(1) 回答每一组询问。
具体来说,从 1 开始逐一处理每个正整数。当处理到数字 x 时,如果数字 x 尚未被标记为不合法,就通过拆位判断它是否合法。如果是合法数字,则将其加入合法数字列表,否则将 x 的所有倍数(包括 x 本身)全部标记为不合法数字;如果数字 x 已经被标记为不合法,则因为数字 x 的所有倍数都已经在之前和数字 x 一同被标为不合法数字,不需要再执行额外操作。
2.The end...
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int t,la,nx[N];
bool f[N];
bool check(int x){while(x!=0){if(x%10==7) return 1;x/=10;}return 0;
}
void pre(){for(int i=1;i<=N;i++){if(f[i]) continue;if(check(i)){for(int j=1;i*j<=N;j++){f[i*j]=1;}continue;}nx[la]=i;la=i;}return;
}
int main(){scanf("%d",&t);pre();while(t--){int x;scanf("%d",&x);if(f[x]) printf("-1\n");else printf("%d\n",nx[x]);}return 0;
}
数列
1.可以想到dp。可以发现,S 的二进制表示位中 1 的数量会涉及进位的问题。由于进位是从低位向高位进行的,所以考虑在 S 中从低位到高位按位 dp(最低位为第 0 位)。
2.设计 dp 状态 f(i,j,k,p) 表示:讨论了 S 从低到高的前 i 位,已经确定了 j 个序列 a 中的元素,S 从低到高前 i 位中有 k 个 1,要向当前讨论位的下一位进位 p。因为从上一个状态转移到 f(i,j,k,p) 细节太多,所以考虑用 f(i,j,k,p) 往后转移。接下来讨论第 i 位(位从 0 开始编号)。假设序列 a 中有 t 个元素为 i,那么就相当于给 S 的第 i 位贡献了 t 个 1,再加上上一位进过来的 p 个 1,总共有 t+p 个 1。可以知道,当前位每两个 1 可以向下一位进 1。所以 (t+p)%2 的结果即为全部进位后当前位是否为 1。同理,向下一位进的 1 的个数即为 ⌊t+p2⌋/2。
f(i,j,k,p) 往后转移的状态为f(i+1,j+t,k+(t+p)%2,⌊2t+p⌋/2)
统计答案呢:对于 i 这一维,由于 a 中元素的范围是 0∼m,所以 S 只用看总共 m+1 位,所以是 m+1。对于 j 这一维,最终 n 个元素都要确定,所以是 n。对于 k 这一维,应该是在 0∼k 之间的。(p 这一维就随便了)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll ans,v[105],dp[105][35][35][16],pv[105][35];
ll C[35][35];
inline void init(int n){for(int i=0;i<=n;i++) C[i][0]=1;for(int i=1;i<=n;i++)for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
inline int popcnt(int n){int res=0;while(n) res+=n&1,n>>=1;return res;
}
int main(){init(30);int n,m,K;scanf("%d%d%d",&n,&m,&K);for(int i=0;i<=m;i++){scanf("%lld",&v[i]);pv[i][0]=1;for(int j=1;j<=n;j++) pv[i][j]=pv[i][j-1]*v[i]%mod;}dp[0][0][0][0]=1;for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)for(int k=0;k<=K;k++)for(int p=0;p<=n>>1;p++)for(int t=0;t<=n-j;t++) dp[i+1][j+t][k+(t+p&1)][t+p>>1]=(dp[i+1][j+t][k+(t+p&1)][t+p>>1]+dp[i][j][k][p]*pv[i][t]%mod*C[n-j][t]%mod)%mod;for(int k=0;k<=K;k++)for(int p=0;p<=n>>1;p++)if(k+popcnt(p)<=K) ans=(ans+dp[m+1][n][k][p])%mod;printf("%lld",ans);return 0;
}
方差
1.直接爆搜(然后对了3个点,剩下的全wa了,明显思路不对(其实是乱搞))得了12分
#include<bits/stdc++.h>
#define int long long
const int base=133,base2=131;
const int mod=10000019,mod2=998244353;
const int maxn=1e4+5;
using namespace std;
unordered_map<int,int>mp,mp2;
vector<int>now;
int a[maxn],b[maxn];
int n,ans=1e18;
int lst[maxn],vis[maxn];
int hash1(){int anss=0;for(int i=1;i<=n;i++){anss=(anss+b[i]*base%mod)%mod;}return anss;
}
int hash2(){int anss=0;for(int i=1;i<=n;i++){anss=(anss+b[i]*base2%mod2)%mod2;}return anss;
}
void check(){for(int i=1;i<=n;i++) b[i]=a[i];for(int i=0;i<now.size();i++){b[now[i]]=b[now[i]-1]+b[now[i]+1]-b[now[i]];}int now=0; for(int i=1;i<=n;i++){now+=b[i];}int noww=0;for(int i=1;i<=n;i++){noww=noww+(n*b[i]-now)*(n*b[i]-now);}noww/=n;ans=min(ans,noww);
}
void dfs(int x){int noww=hash1();int nowww=hash2();if(mp[noww]&&mp2[nowww]) return;mp[noww]=1;mp2[nowww]=1;for(int i=2;i<n;i++){now.push_back(i);check();dfs(x+1);now.pop_back();}
}
signed main(){cin>>n;for(int i=1;i<=n;i++){cin>>a[i];b[i]=a[i];} check();dfs(1);cout<<ans;return 0;
}
2.考虑暴力: 显然可以转为交换差分数组相邻两项。而一个直观的结论是差分数组单谷。考虑搜索差分数组中每种取值中分别有多少在谷两侧。这个做法的时间上限应为 maxp∏(pi+1),其中 pi 为差分数组中值为 i 的元素数。显然,p 是 n−1 的一个划分且大小小于 min(n,o(maxai));易得 pp 中必存在一元素至少为 n−1−maxai。(能得60分)
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {int x = 0, f = 1;char ch = getchar();while(!isdigit(ch)) {if(ch == '-') f = -1;ch = getchar();}while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}return x * f;
}
const int N = 1e4 + 100;
int n, ans = 1e18, a[N], d[N];
inline int calc() {for(int i = 1; i <= n; ++i) a[i] = a[i - 1] + d[i];int R, s1 = 0, s2 = 0;for(int i = 1; i <= n; ++i)s1 += a[i] * a[i], s2 += a[i];R = n * s1 - s2 * s2;return R;
}
signed main() {srand(time(0));n = read();for(int i = 1; i <= n; ++i) a[i] = read(), d[i] = a[i] - a[i - 1];int T = 100;while(T--) {random_shuffle(d + 2, d + 1 + n);int t = 0;int res = 1e18;while(t < 300) {int x = rand() % n + 1, y = rand() % n + 1;if(x == 1 || y == 1) continue;if(x != y) {swap(d[x], d[y]);int tmp = calc();if(tmp < res) {t = 0;res = tmp;} else ++t, swap(d[x], d[y]);} }ans = min(ans, res);}cout << ans << '\n';return 0;
}
3.考虑正解:可以发现:每次操作就是交换差分,那么相当于差分可以随便重排。化一下这个方差的式子,就是 n×∑ai^2−(∑ai)^2。然后 a 同时减小某个数对答案是没有影响的,那么可以钦定 a1=0。尝试去猜结论,发现样例的差分是2 1 2,可以猜测到这个差分是先减小后递增的,(具体的证明不会,感性理解这样数就会尽可能的“均匀”)。
然后就容易想到做法了:先把差分重排,从小到大考虑,那么每个差分要么插在首要么插在尾,但是两个相减的东西很难维护,那么可以想当把一维放到状态里去,经过一些尝试后发现就是 ∑ai,即 dpi,j 表示处理到前 i 个差分,这 i 个差分还原出来的 ai 序列的和是 j,在这个情况下的 ∑ai^2 的最小值,然后你发现无论是插头上还是尾巴上都是可以快速算出新的 ∑ai 和 ∑ai^2 的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e18,mod=1e9+7;
const int N=10010;
int a[N],d[N];
ll f[2][500010];
int main(){int n;scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);int m=n-1;for(int i=1;i<=m;i++){d[i]=a[i+1]-a[i];}sort(d+1,d+m+1);int s=0;for(int i=1;i<=n;i++) s+=d[i]*i;int now=0,u=0;for(int j=0;j<=s;j++) f[u][j]=INF;f[0][0]=0;for(int i=1;i<=m;i++){if(d[i]==0) continue;u^=1;now+=d[i];for(int j=0;j<=s;j++){f[u][j]=INF;int k=j-i*d[i];if(k>=0) f[u][j]=min(f[u][j],f[u^1][k]+(ll)i*d[i]*d[i]+(ll)2*k*d[i]);k=j-now;if(k>=0) f[u][j]=min(f[u][j],f[u^1][k]+now*now);}}ll ans=INF;for(int j=0;j<=s;j++){if(f[u][j]<INF)ans=min(ans,n*f[u][j]-(ll)j*j);}cout<<ans<<endl;return 0;
}