做题记录
dp
Round Subset
末尾 \(0\) 的个数只与因子中 \(5\) 和 \(2\) 的数量有关。
\(f_{i,j,k,l}\) 表示当前为第 \(i\) 个数,选了 \(j\) 个数,有 \(k\) 个 \(5\) 作为因子,有 \(l\) 个 \(2\) 作为因子时可不可以选。
但是空间太大了( \(k\) 大约需要开到 800 多),可以把最后一维作为 dp 数组的含义,即 \(f_{i,j,k}\) 表示当前为第 \(i\) 个数,选了 \(j\) 个数,有 \(k\) 个 \(5\) 作为因子时,作为因子的 \(2\) 的数量。然后就变成了一个多维背包。
但是啊!空间还是爆了,所以我们进行一个滚动数组把第一维滚了。
#include <iostream>
#include <cstring>
#define int long long
const int M = 200+10;
int n,k;
int a[M];
int f[M][8001];
int fi[M],tw[M];
int ans;
inline void makefat(int i){
int x=a[i];
while((x%5)==0){
x/=5;
fi[i]++;
}
while((x%2)==0){
x/=2;
tw[i]++;
}
return;
}
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
signed main(){
memset(f,-0x3f,sizeof f);
n=read(),k=read();
for(int i = 1;i <= n;i++) a[i]=read(),makefat(i);
f[0][0]=0;
for(int i = 1;i <= n;i++){
for(int j = k;j >= 1;j--){
for(int l = 8000;l>=fi[i];l--){
f[j][l]=std::max(f[j][l],f[j-1][l-fi[i]]+tw[i]);
}
}
}
for(int i = 0;i <= 8000;i++){
ans=std::max(ans,std::min(f[k][i],i));
}
printf("%lld",ans);
return 0;
}
学到了什么?:将一维转化为含义。
数据结构
线段树
P3801 红色的幻想乡
发现可以用容斥原理。
用两个线段树分别维护行上的红雾数量和列上的红雾数量。单点修改,区间求值。设有红雾的行数为 \(h\) ,有红雾的列数为 \(l\) 。根据容斥原理,可得出红雾数为 \(h\times(y_2-y_1+1)+l\times(x_2-x_1+1)-2\times l\times h\)
#include <iostream>
#define int long long
#define ls (x<<1)
#define rs (x<<1|1)
const int M = 1e5+10;
int n,m,q;
struct Seg
{
struct Tree
{
int l,r,num,sum;
}t[M<<2];
inline void pushUp(int x)
{
t[x].sum=t[ls].sum+t[rs].sum;
}
void build(int x,int l,int r)
{
t[x].l=l,t[x].r=r;
if(l==r){
t[x].num=0;
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
return ;
}
void update(int x,int l,int r)
{
if(t[x].l>= l&&t[x].r <=r){
t[x].num^=1;
t[x].sum^=1;
return;
}
int mid=(t[x].l+t[x].r)>>1;
if(mid >= l) update(ls,l,r);
if(mid < r) update(rs,l,r);
pushUp(x);
return;
}
int getSum(int x,int l,int r)
{
if(t[x].l>= l&&t[x].r <=r)
return t[x].sum;
int mid=(t[x].l+t[x].r)>>1,ans=0;
if(mid >= l) ans+=getSum(ls,l,r);
if(mid < r) ans+=getSum(rs,l,r);
return ans;
}
}hang,lie;
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
signed main(){
n=read(),m=read(),q=read();
hang.build(1,1,n);
lie.build(1,1,m);
while(q--){
int mod=read();
if(mod==1){
int x=read(),y=read();
hang.update(1,x,x);
lie.update(1,y,y);
}
if(mod==2){
int x1=read(),y1=read(),x2=read(),y2=read();
int h=hang.getSum(1,x1,x2),l=lie.getSum(1,y1,y2);
printf("%lld\n",h*(y2-y1+1)+l*(x2-x1+1)-2*h*l);
}
}
return 0;
}
P4513 小白逛公园
就是山海经,但是山海经不想写。
单点修改,区间求最大子段和。考虑一个区间的最大子段和可能为其左子区间的最大子段和,右子区间的最大子段和,或两个子区间各取一部分的和。
因为第一,二种情况可以转化为子问题。所以只对第三种情况分类讨论:
- 取左区间的最大后缀和和右区间的最大前缀和。
- 取左区间的区间和和右区间的最大前缀和。
- 取左区间的最大后缀和和右区间的区间和。
所以需要维护最大前缀和,最大后缀和,区间和与最大子段和。
注意合并时细节(?)
#include <iostream>
#define int long long
#define ls (q<<1)
#define rs (q<<1|1)
const int M = 5e5+10;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n,m;
int a[M];
namespace Seg
{
struct Tree
{
int lsum,rsum,sum,ans;
}t[M<<2];
inline void pushUp(int q)
{
t[q].sum=t[ls].sum+t[rs].sum;
t[q].lsum=std::max(t[rs].lsum+t[ls].sum,t[ls].lsum);
t[q].rsum=std::max(t[ls].rsum+t[rs].sum,t[rs].rsum);
t[q].ans=std::max(t[rs].lsum+t[ls].rsum,std::max(t[ls].ans,t[rs].ans));
return ;
}
void build(int q,int l,int r)
{
if(l==r){
t[q].rsum=t[q].lsum=t[q].sum=t[q].ans=a[l];
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushUp(q);
return ;
}
void change(int q,int l,int r,int x,int y,int num)
{
if(l >= x&&r <= y){
t[q].rsum=t[q].lsum=t[q].sum=t[q].ans=num;
return ;
}
int mid =(l+r)>>1;
if(mid >= x) change(ls,l,mid,x,y,num);
if(mid < y) change(rs,mid+1,r,x,y,num);
pushUp(q);
return ;
}
Tree getMaxSum(int q,int l,int r,int x,int y)
{
if(l >= x&&r <= y)
return t[q];
int mid = (l+r)>>1;
bool f1=0,f2=0;
if(mid >= x) f1=1;
if(mid < y) f2=1;
if(f1==1&&f2==0) return getMaxSum(ls,l,mid,x,y);
else if(f1==0&&f2==1) return getMaxSum(rs,mid+1,r,x,y);
else{
Tree lt=getMaxSum(ls,l,mid,x,y),rt=getMaxSum(rs,mid+1,r,x,y),nt;
nt.sum=lt.sum+rt.sum;
nt.lsum=std::max(rt.lsum+lt.sum,lt.lsum);
nt.rsum=std::max(lt.rsum+rt.sum,rt.rsum);
nt.ans=std::max(rt.lsum+lt.rsum,std::max(lt.ans,rt.ans));
return nt;
}
}
}
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
signed main(){
n=read(),m=read();
for(int i = 1;i <= n;i++) a[i]=read();
Seg::build(1,1,n);
while(m--){
int k=read(),x=read(),y=read();
if(k==1){
if(x>y) std::swap(x,y);
printf("%lld\n",Seg::getMaxSum(1,1,n,x,y).ans);
}
else{
Seg::change(1,1,n,x,x,y);
}
}
return 0;
}
合并合并合并合并合并合并合并合并合并合并合并合并合并合并
主席树
P4587 [FJOI2016] 神秘数
暴力做法就是遍历 \([l,r]\) 。
设 \(a_i\) 表示遍历到的下一个数,\(x\) 表示当前答案。
可以得出:
- 当 \(x < a_i\) 时,无贡献。
- 当 \(x \ge a_i\) 时,\(x = a_i+x+1\)
时间复杂度 \(O(nm)\) 。过不了,考虑优化。
设 \(\_max\) 为当前可以表示的最大值, \(\_min\) 为当前区间中数的最大值。初始 \(\_max=\_min=0\) 。
根据上面的发现 \(2\) 可以知道需要加入的数一定小于等于 \(\_max+1\) ,又因为小于等于 \(\_min\) 的数已经加入,所以每次加入数值位于 \([\_min+1,\_max+1]\) 区间内的数的和。设次和为 \(add\) ,每次令 \(\_min=\_max+1\) \(\_max=\_max+add\) 。当 \(add=0\) 时退出
#include <iostream>
#include <algorithm>
#define int long long
#define frer freopen("in.in","r",stdin);
#define frew freopen("out.out","w",stdout);
const int M = 1e5+10;
int n,m,maxn,cnt;
int d[M],root[M];
int _min,_max;
namespace Pseg
{
struct Tree
{
int ls,rs,sum;
}t[M<<5];
inline void pushUp(int q)
{
t[q].sum=t[t[q].ls].sum+t[t[q].rs].sum;
}
void update(int p,int q,int l,int r,int x)
{
if(l==r){
t[q].sum=t[p].sum+x;
return ;
}
t[q].ls=t[p].ls,t[q].rs=t[p].rs;
int mid=(l+r)>>1;
if(mid >= x) update(t[p].ls,t[q].ls=++cnt,l,mid,x);
else update(t[p].rs,t[q].rs=++cnt,mid+1,r,x);
pushUp(q);
return ;
}
int getSum(int p,int q,int l,int r,int x,int y)
{
if(l >= x&&r <= y)
return t[q].sum-t[p].sum;
int mid=(l+r)>>1,ans=0;
if(mid >= x) ans+=getSum(t[p].ls,t[q].ls,l,mid,x,y);
if(mid < y) ans+=getSum(t[p].rs,t[q].rs,mid+1,r,x,y);
return ans;
}
}
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
signed main(){
n=read();
for(int i = 1;i <= n;i++) d[i]=read(),maxn=std::max(d[i],maxn);
for(int i = 1;i <= n;i++) Pseg::update(root[i-1],root[i]=++cnt,1,maxn,d[i]);
m=read();
while(m--){
int x=read(),y=read();
_min=0,_max=0;
while(1){
int sum;
if(_min>=maxn) sum=0;
else sum=Pseg::getSum(root[x-1],root[y],1,maxn,_min+1,_max+1);
if(!sum) break;
_min=_max+1;
_max+=sum;
}
printf("%lld\n",_max+1);
}
return 0;
}
数论
龙哥的问题
求 \(\sum\limits_{i=1}^n\gcd(i,n)\)
想到与 \(\gcd(i,n)=x\) 的数量有关。
设
因为 \(\varphi(x)=\sum\limits_{i=1}^n[\gcd(i,n)=1]\) ,所以 \(C(x)=\varphi(\tfrac{n}{x})\)
原式可以转化为
#include <iostream>
#include <cmath>
#define int long long
int n,ans;
inline int phi(int x)
{
int res=x;
for(register int i = 2;i*i<=x;++i){
if(x%i==0){
res=res*(i-1)/i;
while(x%i==0) x/=i;
}
}
if(x>1) res=res*(x-1)/x;
return res;
}
inline void work(int x)
{
for(register int i = 1;i*i<x;i++){
if(x%i==0){
ans+=phi(x/i)*i+phi(i)*x/i;
}
}
if((int)std::sqrt(x)*(int)std::sqrt(x)==x)ans+=phi(x/(int)std::sqrt(x))*(int)std::sqrt(x);
printf("%lld",ans);
}
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
signed main(){
n=read();
work(n);
return 0;
}
这算哪种题啊
种树
\(a_i\) 可只因分解为 \(a_i={p_1}^{k_1} \cdot {p_2}^{k_2}···{p_{m_i}}^{k_{m_i}}\) 。
则 \(a_i\) 的正因数个数为 \(\prod\limits_{j=1}^{m_i}k_j+1\) 。因为 \(p_j\) 一共会出现 \(k_j+1\) 次。(不选算一种)
答案可转化为 求 \(\prod\limits_{i=1}^n\prod\limits_{j=1}^{m_i}k_{i,j}+1\) 。其中 \(m_i\) 表示第 \(i\) 个数的质因子数,\(k_{i,j}\) 表示第 \(i\) 个数的第 \(j\) 个质数的指数。
可知当将一个质因子的指数加一,贡献为 \(\dfrac{\prod\limits_{i=1}^n\prod\limits_{j=1}^{m_i}k_{i,j}+1}{k+1}\) 。即指数越小贡献越大。
考虑贪心。
将 \(w\) 质因分解,对于每个质因数遍历一遍 \(p\) 数组,求出指数最小的质因数,乘上当前质因数。最后统一处理出答案。
需要注意质因分解不完全的情况,要最后单独弄一下。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define int long long
const int M = 1e4+10;
const int Mod = 998244353;
const int inf = 0x3f3f3f3f3f3f3f3f;
inline int MAX(int a,int b){return a>b?a:b;}
inline int MIN(int a,int b){return a<b?a:b;}
inline int ABS(int a,int b){return (a-b)>0?a-b:b-a;}
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
int n,w,ans=1,t[M];
inline void input()
{
n=read(),w=read();
for(int i = 1;i <= n;i++) t[i]=read();
}
inline void work()
{
for(int i = 2;i*i <= w;i++){
while(w%i==0){
int ch=0,minn=inf;
w/=i;
for(int j = 1;j <= n;j++){
int cnt=0,x=t[j];
while(x%i==0) x/=i,cnt++;
if(cnt<minn) minn=cnt,ch=j;
}
t[ch]*=i;
}
}
if(w>1){
int ch=0,minn=inf;
for(int i = 1;i <= n;i++){
int cnt=0,x=t[i];
while(x%w==0) x/=w,cnt++;
if(cnt<minn) minn=cnt,ch=i;
}
t[ch]*=w;
}
for(int i = 1;i <= n;i++){
for(int j = 2;j*j<=t[i];j++){
int cnt=0;
if(t[i]%j==0){
while(t[i]%j==0) t[i]/=j,cnt++;
}
ans=ans*(cnt+1)%Mod;
}
if(t[i]>1) ans=ans*2%Mod;
}
printf("%lld",ans);
}
signed main(){
input();
work();
return 0;
}