十月总结
10.11 广二23
T1:计数、容斥原理
有一个计数的做法,大致做法是在最后面的开头统计,然后要求后面不能出现,这样贡献就是唯一的,需要fail树上跑下来dfn这样
容斥原理就比较直接,加上序列中有一个开头的,减去有两个开头的,加上有三个开头的...
我们假定一个位置集合S,我们只需要保证相临的位置的贡献即可,若它们两个没交,那贡献为它们两个中间没有被钦定的,若有交判断合不合法即可。
当然这个还需要考虑开头结尾因为是循环数组,发现这个贡献只与mx-mn有关,这启发我们去根据长度DP转移,而且每个位置能填的数都一样,所以我们用这个长度在好多种位置开始。
商安泽写法:就是前面说的计数。我们考虑暴力枚举最后一个开始位置,我们DP当前是一个什么状态,枚举下一个填什么字符,暴力跳nxt数组更新状态。
现在我们把fail树建出来。
现在我们考虑枚举下一位的状态,现在这个状态可能由对应节点的子树转移过来,那我们子树求和,再把不合法的剪掉即可
点击查看代码
int Fm(int a,int b){int s=1;while(b){if(b&1)s=s*a%P;a=a*a%P;b>>=1;}return s;}
int n,m,K,b[N],pw[N];
int xs[N],f[N],g[N];
bool ok[N];
bool MB_2;
signed main() {
cin>>n>>m>>K;
pw[0]=1;
for(int i=1;i<=n;i++) pw[i]=pw[i-1]*K%P;
for(int i=1;i<=m;i++) b[i]=read();
for(int i=1;i<m;i++) {
ok[i]=1;
for(int j=1;j<=m-i;j++) if(b[j]^b[i+j]) ok[i]=0;
}
for(int i=1;i<=n;i++) xs[i]=(i<m?ok[i]:pw[i-m]);
f[0]=1;
for(int i=1;i<n;i++) {
for(int j=0;j<i;j++)
f[i]=(f[i]+f[j]*xs[i-j])%P;
f[i]=(-f[i]+P)%P;
}
for(int i=1;i<n;i++) f[i]=(f[i]*xs[n-i])%P;
int ans=n*pw[n-m]%P;
for(int i=1;i<n;i++) ans=(ans+f[i]*(n-i))%P;
cout<<ans;
return 0;
}
10.13 夏增锐
T1 简单计数
之前貌似有个类似的题,只能说掌握的及其不牢固。
考虑尽可能靠后的匹配子序列S,枚举起点,起点前面当然随便填,后面相当于我们要选出n-1个位置,为了保证这个匹配是最靠后的,s中相邻两个字符在T中的间隔中不能出现靠前的那一个字符
即
T2 线段树 容斥原理
首先如果k=1,那么变成了一个区间加减,统计全局不为0的位置就行。这个具体来讲就是维护最小值和最小值个数,如果最小值为0就减去最小值个数,否则就是区间长度。扫描线一下。
k多了的情况,就考虑容斥,因为我们相当于求交集,就用一堆并集容斥就行,具体的,若并集大小为奇数,就加上,不然就减去。
T3 贪心 动态规划
随便贪一下就行,注意考场切莫急躁,注意细节,选择合适的方法
T4 Ad-hoc 构造
观察原始式子发现一定大于d。设 \(a \oplus b \oplus c \oplus d=d+x\) ,为了方便我们令d的后几位为0,x为1
开始构造,以下构造正确性显然不证:
设p为a二进制下最高位
\(a \equiv 0 \pmod 2\) : \(b=a+2^p,c=2^{p+1}+2^p+1\)
\(a \equiv 1 \pmod 2\) : \(b=a+2^p-1,c=2^{p+1}+2^p\)
10.17
- P12865 trick:冒泡排序结论
经历c轮冒泡排序后,\([1,l]\) 内的数是 \([1,l+c]\) 的前 l 小值,证明显然,每轮,小的数最多往前走1,大的数会出去
-
返祖边找环,无向图找环可以找返祖边。
-
ht P10702
首先每个正整数都有斐波那契分解(每次减去比他小且最大的斐波那契数),且其他所有分解都能由这个转移过来,发现如果有两个连续的1,高位的就不能下放,具体的看代码
点击查看代码
//^ v ^ "Surpassing never ends!"
//"Beyond Limits: The Journey Never Ends"
//超越极限 , 征途永续
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int N=5e5+500,M=1e15,V=5762005;
const ll P=998244353,inf=3e9;
int read() {
int x=0;short f=1;char s=getchar();
while(s<48||s>57){f=s=='-'?-1:1;s=getchar();}
while(s>=48&&s<=57){x=x*10+s-48;s=getchar();}
return x*f;
}
mt19937 rnd(time(0));
bool MB_1;
string s;
struct GJ {
int a[1010];
void init() { memset(a,0,sizeof(a)); }
friend bool operator <= (const GJ&a,const GJ&b) {
for(int i=1000;i>=0;i--) if(a.a[i]<b.a[i]) return 1;else if(a.a[i]>b.a[i]) return 0;
return 1;
}
friend GJ operator +(const GJ&a,const GJ&b) {
GJ c;c.init();
for(int i=0;i<=1000;i++) {
c.a[i]+=a.a[i]+b.a[i];
c.a[i+1]+=c.a[i]/M;
c.a[i]%=M;
}
return c;
}
}sum,n,b[50100];
int Lim,a[N];
int f[N][3];
int get(int p,int num) {
if(p<=2) {
if(num<=1) return 1;
return 0;
}
if(f[p][num]!=-1) return f[p][num];
if(num==2) {
if(a[p-1]) return f[p][num]=0;
else return f[p][num]=get(p-2,a[p-2]+1);
}
else if(num==1) {
if(a[p-1]) return f[p][num]=get(p-1,a[p-1]);
else return f[p][num]=(get(p-1,a[p-1])+get(p-2,a[p-2]+1))%P;
}
else return f[p][num]=get(p-1,a[p-1]);
}
bool MB_2;
signed main() {
cerr<<fabs(&MB_2-&MB_1)*1.0/(1024*1024)<<'\n';
freopen("winter.in","r",stdin);
freopen("winter.out","w",stdout);
cin>>s;
int nw=0;
for(int i=s.size()-1;i>=0;i-=15) {
int l=i,r=max(i-15+1,0ll);
for(int j=r;j<=l;j++) {
n.a[nw]=n.a[nw]*10+s[j]-'0';
}
nw++;
}
// for(int i=0;i<=nw;i++) cout<<n.a[i]<<" ";
b[0].a[0]=1,b[1].a[0]=1;
for(int i=2;b[i-1]<=n;i++) {
Lim=i;
b[i]=b[i-1]+b[i-2];
// for(int j=0;j<=1;j++) cout<<b[i].a[j]<<" ";cout<<'\n';
}
for(int i=Lim;i>=1;i--) {
f[i][0]=f[i][1]=f[i][2]=-1;
GJ j=sum+b[i];
if(j<=n) a[i]=1,sum=j;
// cout<<a[i]<<" ";
}
for(int i=Lim;i>=1;i--) {
if(a[i]) { cout<<get(i,a[i]); return 0; }
}
return 0;
}
- MX星航九月份ST1 :
我们先把这个平方拆成二次函数
\(F_{[L,R]}(x) = \sum_{i=L}^{R} (A_i - B_ix)^2 = Q_{[L,R]} x^2 - 2 P_{[L,R]} x + C_{[L,R]}\)
这个式子在 \(t_{[L,R]} = \dfrac{P_{[L,R]}}{Q_{[L,R]}}\) 取到最小值。
我们能够说明最终答案一定满足 \(B_i\) 相等的一段是取到 \(t_{[L,R]}\) 的。因为如果不是这个值。那么它一定能在左边或右边的B值取到比当前更优的值,这样他又会和前面或后面的区间合并成一个新区间,成为一个新的同质化问题,一直这样下去就好了。
这样还不够。我们补充一个结论。
设某个区间最优点为 \(v\)
若 $v_i < v_{i+1} $ ,则它们最后的取值 \(x_i=x_{i+1}\)
微调法即可,不管哪种情况都会有一个x往对应的v挪移
广二24
T1:计数
有点简单吧,就把质因子分配即可,场切
T2:诈骗,性质
等价类的划分耐人寻味,以后考试可以多注意这点(即会有很多无用或等价状态)
T3:计数,连续段DP
显然正负可以分开讨论
先考虑部分分,就是都为正,把大数( \(x > \sqrt C\) )和小数分开考虑,显然大数两边只能是某些小数,我们把大数从大到小,小数从小到大处理出来,让大数两边只能是他之前加入的小数,而小数没有其他限制。
那么大数能干的显然只有合并两个小数段,DP小数段数即可。注意这个需要左右端点
点击查看代码
void Solve(int n,int a[],int f[][N]) {
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) w[i]=0;
int pos=0;
for(int i=1;i<=n;i++) {
if(a[i]*a[i-1]<=K) pos=i;
}
int o=0;f[0][0]=1;
int p=pos+1;
for(int i=pos;i>=1;i--) {
while(a[p]*a[i]<=K&&p<=n) {
o^=1;
for(int j=0;j<=n;j++) f[o][j]=0;
for(int j=0;j<=n;j++) if(f[o^1][j]) {
add(f[o][j+1],f[o^1][j]*(j+1));
}
p++;
}
o^=1;
for(int j=0;j<=n;j++) f[o][j]=0;
for(int j=0;j<=n;j++) if(f[o^1][j]) {
add(f[o][j+1],f[o^1][j]*(j+1));
if(j) add(f[o][j-1],f[o^1][j]*(j-1));
add(f[o][j],f[o^1][j]*(2*j));
}
}
if(!o) for(int j=0;j<=n;j++) f[1][j]=f[0][j];
}
发现有正负之后可以存在不合法的段。那我们换一种DP方式,DP不合法的段数。改变加入顺序:从小往大加入大数,从大往小加入小数,这时大数与前边的数的乘积都不合法,只能新开一段,最后把正负拼起来即可。
点击查看代码
void Solve(int n,int a[],int f[][N]) {
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) w[i]=0;
int pos=0;
for(int i=1;i<=n;i++) {
if(a[i]*a[i-1]<=K) pos=i;
}
int o=0;f[0][0]=1;
int p=pos+1;
for(int i=pos;i>=0;i--) {
while(a[p]*a[i]<=K&&p<=n) {
o^=1;
for(int j=0;j<=n;j++) f[o][j]=0;
for(int j=0;j<=n;j++) if(f[o^1][j]) {
add(f[o][j+1],f[o^1][j]*(j+1));
}
p++;
}
if(!i) continue;
o^=1;
for(int j=0;j<=n;j++) f[o][j]=0;
for(int j=0;j<=n;j++) if(f[o^1][j]) {
add(f[o][j+1],f[o^1][j]*(j+1));
if(j) add(f[o][j-1],f[o^1][j]*(j-1));
add(f[o][j],f[o^1][j]*(2*j));
}
}
if(!o) for(int j=0;j<=n;j++) f[1][j]=f[0][j];
}
bool MB_2;
signed main() {
cerr<<fabs(&MB_2-&MB_1)*1.0/(1024*1024)<<'\n';
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
cin>>n>>K;
for(int i=1;i<=n;i++) {
int x=read();
if(x>0) a[++le1]=x;
else x=-x,b[++le2]=x;
}
Solve(le1,a,f),Solve(le2,b,g);
int ans=0;
for(int i=1;i<=le1;i++) {
add(ans,f[1][i]*g[1][i]*2);
add(ans,f[1][i]*g[1][i-1]);
add(ans,f[1][i]*g[1][i+1]);
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号