下午开始停课了,晚上的比赛不算简单,卡常什么的也是让人心烦,排名刚好如自己预言。
回文数
题目描述
题解(错位相减法+快速幂+求逆元)
这是一道数列求前缀和的问题。学过数列的都能用错位相减法秒杀。
明显题目要求的就是
这就是一个等差数列乘一个等比数列,直接错位相减,求出来是
直接快速幂。由于要取模,而模数 233333=353∗661 ,于是用欧拉定理或扩欧求逆元,逆元是 25926 。
时间复杂度 O(Tlogn) 。
代码
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MOD 233333
using namespace std;
int T, n;
typedef long long LL;
LL Pow(LL x, int y){
LL res = 1LL;
while(y){
if(y & 1) res = res * x % MOD;
x = x * x % MOD;
y >>= 1;
}
return res;
}
int main(){
freopen("2255.in", "r", stdin);
freopen("2255.out", "w", stdout);
scanf("%d", &T);
while(T --){
scanf("%d", &n);
n = (n + 1) >> 1;
LL ans1 = ((Pow(10LL, n-1) - 1LL + MOD) % MOD * 25926LL % MOD * -20LL + MOD) % MOD;
LL ans2 = ((Pow(10LL, n) * ((2LL * n - 1LL + MOD) % MOD)) % MOD - 1LL + MOD) % MOD;
LL ans = (ans1 + ans2 + MOD) % MOD;
printf("%lld\n", ans);
}
return 0;
}
购物
题目描述
题解(动态规划)
题目中 happy 值的下界为1。
由于平方的和不大于和的平方,很容易想到一种贪心:每次选取 happy 值的和最大的一段区间先做,然后将相交的区间删去重合部分,然后继续做。一开始要先将区间数量变成不超过 n ,保证左指针递增同时右指针也递增。
这样贪能拿扶我起来我还能贪
我们发现
n
比较小,于是以
然后一个
其中 sum 是前缀和, j 的取值范围就是
值得注意的是,如果没有合法的转移, dp[i]=dp[i−1] 。
总结:如果题目要求具有次序,不妨找一个确定次序的状态(或强制其有序)来进行 DP ,一般 DP 的题要消除次序限制,只考虑前一个状态对后面某个状态的贡献。
代码
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 5005
#define maxm 1000010
using namespace std;
typedef long long LL;
int n, m, Left[maxn];
LL happy[maxn];
LL dp[maxn], sum[maxn];
struct Data{
int l, r;
bool operator < (const Data& Q) const{
return r > Q.r;
}
}q[maxm];
int main(){
freopen("2256.in", "r", stdin);
freopen("2256.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &happy[i]);
for(int i = 1; i <= n; i++) sum[i] = sum[i-1] + happy[i];
for(int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r);
sort(q+1, q+m+1);
for(int i = n, j = 1, re = n+1; i >= 1; i--){
while(j <= m && q[j].r >= i){
re = min(re, q[j].l);
j ++;
}
Left[i] = re;
}
for(int i = 1; i <= n; i++){
if(Left[i] > i) dp[i] = dp[i-1];
for(int j = Left[i]; j <= i; j++)
dp[i] = max(dp[i], dp[j-1] + (sum[i] - sum[j-1]) * (sum[i] - sum[j-1]));
}
printf("%lld\n", dp[n]);
return 0;
}
宗教
题目描述
题解(后缀数组+RMQ/二分+hash/暴力+特判/FFT+优化??)
本题较难,题目可以看作求 K 次最长公共前缀。。。
直接用加了底层优化的暴力+特判即可过。当然这是一种“无味”的过法。正解是二分+
后缀数组+ RMQ 的坑也日后再填, NOIP 考后缀数组的概率较小。另外考场上我写了 26 次的 FFT ,求汉明距离就是个卷积。明显差一定反过来卷,然后把不等号变成乘号就好了。和 UVA 某题一样,但是 FFT 常数太大过不了这题的,比暴力还低分。不知道有没有可靠的优化能救民于水火。
总之这题就留坑待填吧。
代码(暴力+特判)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define maxn 200010
using namespace std;
char s1[maxn], s2[maxn];
int K, len1, len2, ans, cnt;
int main(){
freopen("2257.in", "r", stdin);
freopen("2257.out", "w", stdout);
scanf("%s%s", &s1, &s2);
scanf("%d", &K);
len1 = strlen(s1);
len2 = strlen(s2);
bool cheat = true;
for(int i = 1; i < len1; i++) if(s1[i] != s1[i-1]) cheat = false;
for(int i = 1; i < len2; i++) if(s2[i] != s2[i-1]) cheat = false;
if(cheat){
printf("%d\n", len1 - len2 + 1);
return 0;
}
for(register int i = 0; i < len1-len2+1; i++){
cnt = 0;
for(register int j = 0; j < len2; j++){
if(s1[i+j] != s2[j]){
cnt ++;
if(cnt > K) break;
}
}
if(cnt <= K) ans ++;
}
printf("%d\n", ans);
return 0;
}
代码(FFT)
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define MAXN 200010
using namespace std;
const double PI = acos(-1.0);
int len1, len2, n, K;
char s1[MAXN], s2[MAXN];
int P[MAXN], ans;
struct Complex{
double real, image;
Complex() {}
Complex(double _real, double _image){
real = _real;
image = _image;
}
friend Complex operator + (Complex A, Complex B){return Complex(A.real + B.real, A.image + B.image);}
friend Complex operator - (Complex A, Complex B){return Complex(A.real - B.real, A.image - B.image);}
friend Complex operator * (Complex A, Complex B){return Complex(A.real * B.real - A.image * B.image, A.real * B.image + A.image * B.real);}
}a[MAXN<<2], b[MAXN<<2], c[26][MAXN<<2];
void Get_P(){
for(register int i = 1, t = 1; i < MAXN; i++){
if(t < i) t <<= 1;
P[i] = t;
}
}
void Reverse(Complex *A){
for(register int i = 1; i < n-1; i++){
int j = 0;
for(register int k = 1, tmp = i; k < n; k <<= 1, tmp >>= 1)
j = ((j << 1) | (tmp & 1));
if(j > i) swap(A[i], A[j]);
}
}
void FFT(Complex *A, int n, int DFT){
Reverse(A);
for(register int s = 1; (1<<s) <= n; s++){
int m = (1 << s);
Complex wm = Complex(cos(DFT*2*PI/m), sin(DFT*2*PI/m));
for(register int k = 0; k < n; k += m){
Complex w = Complex(1, 0);
for(register int j = 0; j < (m>>1); j++){
Complex u = A[j + k], t = w * A[j + k + (m>>1)];
A[j + k] = u + t;
A[j + k + (m>>1)] = u - t;
w = w * wm;
}
}
}
if(DFT == -1) for(register int i = 0; i < n; i++) A[i].real /= n, A[i].image /= n;
}
void Work(char x, int id){
for(register int i = 0; i < len1; i++) a[i] = Complex(s1[i]==x, 0);
for(register int i = len1; i < n; i++) a[i] = Complex(0, 0);
for(register int i = 0; i < len2; i++) b[len2-i-1] = Complex(s2[i]==x, 0);
for(register int i = len2; i < n; i++) b[i] = Complex(0, 0);
FFT(a, n, 1);
FFT(b, n, 1);
for(register int i = 0; i < n; i++) c[id][i] = a[i] * b[i];
FFT(c[id], n, -1);
}
int main(){
freopen("2257.in", "r", stdin);
freopen("2257.out", "w", stdout);
Get_P();
scanf("%s%s", &s1, &s2);
scanf("%d", &K);
len1 = strlen(s1);
len2 = strlen(s2);
if(len1 < len2){
puts("0");
return 0;
}
n = P[max(len1, len2)] << 1;
for(register int i = 0; i < 26; i++) Work(i + 'a', i);
for(register int i = len2-1; i < len1; i++){
int sum = 0;
for(register int j = 0; j < 26; j++)
sum += (int)(c[j][i].real+0.5);
if(len2 - sum <= K) ans ++;
}
printf("%d\n", ans);
return 0;
}
总结
这次考试之前教练叫我们在做题前预估解题时间并记录真正的完成时间。我在这次比赛中时间并不是调节得很恰当,第一题这样的水题因为一点细节做了那么久时间,后面的时间就略显不足。
另外第二题想贪心不对就没想过 DP ,钻进一个地方,这是很致命的,考试经验还需要增加。
第三题这样的题目也没有先写个暴力交一下看看刷了几次。
海底月是天上月