做题记录

· · 个人记录

中山市选 树

一道树型DP题,类似树型背包的套路

定义 dp_{i,0/1,0/1} 为以 i 为根的子树,按不按,合并完当前子树时候其亮暗情况,注意考虑完整个子树(除了根节点)应该全亮。

首先初始化(没有合并子树的情况)

分类讨论

$dp_{i,0,1}$可能情况:当前点亮,没按子树;当前点暗,按了子树。子树亮。 $dp_{i,1,0}$可能情况:当前点亮,按了子树;当前点暗,按了子树。子树暗。 $dp_{i,1,1}$可能情况:当前点亮,不按子树;当前点暗,按了子树。子树暗。 转移即可 ARC146B 考虑二进制拆分,贪心。 优先考虑高位,如果高位取了不成立,则取高位并取一些低位必定不成立(单调性)。 具体来讲,高位能取则取(这样必定比低位全取优)。 考虑check,对于每个数$Y$考虑,要让我们要check的数$X$一些0的位置变成1使得$X-Y$,同样采用二进制拆分的思路,从高位向低位,如果低位全改都不能使$X>Y$ ,则必须要改,否则不用改,因为必然有更优的选择 最后排序取前 $k$ 个看花费总和是否超过 $m
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int n,k;
long long m;
long long A[MAXN];
long long P[40];
long long B[MAXN];
long long pre[40];
bool check(long long id){
    for(int i=1;i<=n;i++){
        B[i]=id;
        pre[0]=0;
        for(int j=0;j<=32;j++){
            pre[j+1]=pre[j];
            if((id&P[j])==0)pre[j+1]+=P[j];
        }
        for(int j=32;j>=0;j--){
            if((B[i]&P[j])!=0){
                continue;
            }
            if(B[i]+pre[j]<A[i]){
                B[i]+=P[j];
            }
        }
        B[i]-=A[i];
    }
    sort(B+1,B+n+1);
    long long sum=0;
    for(int i=1;i<=k;i++){
        sum+=B[i];
        if(sum>m)return false;
    }
    return true;
}
int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)cin>>A[i];
    P[0]=1;
    for(int i=1;i<=32;i++)P[i]=P[i-1]*2;
    long long Ans=0;
    for(int i=32;i>=0;i--){
        if(check(Ans+P[i])){
            Ans+=P[i];
        }
    }
    cout<<Ans<<endl; 
}

https://atcoder.jp/contests/dp/tasks/dp_z

Frog3

斜率模板题,但码了半天

出现的bug:忘记将 i 加进单调队列

出队时符号反(应该是不符合要求的出队,如果符合要求了就不出队break)

羊羊列队

一道斜率优化DP题

需要注意的几点

1.dp要赋初值

2.斜率优化之前要注意去重

3.凸包方向不能建反

4.对于每一个j建一个凸包,转移的时候注意j还是j-1

5.注意队头队尾不能写反

6.注意循环顺序(后效性)

#include<bits/stdc++.h>
using namespace std;
const long long INF=1e18;
int n,m;
long long a[10010];
long long b[10010];
long long dp[10010][1010];
deque<long long> dq[1010]; 
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+n+1);
    int cnt=1;
    b[1]=a[1];
    for(int i=2;i<=n;i++){
        if(a[i]!=a[i-1]){
            b[++cnt]=a[i];
        }
    }
    for(int i=0;i<=cnt;i++){
        for(int j=0;j<=m;j++)dp[i][j]=INF;
    }
    dp[0][0]=0;
    dq[0].push_back(0);
    for(int i=1;i<=cnt;i++){
        for(int j=m;j>=1;j--){
            while(dq[j-1].size()>=2){
                int fr=dq[j-1].front();
                dq[j-1].pop_front();
                int fr2=dq[j-1].front();
                //if(j==2)cout<<fr<<" "<<fr2<<endl;
                if(b[i]*(-2*b[fr2+1]+2*b[fr+1])<dp[fr][j-1]+b[fr+1]*b[fr+1]-dp[fr2][j-1]-b[fr2+1]*b[fr2+1]){
                    continue;
                }
                else {
                    dq[j-1].push_front(fr);
                    break;
                }
            }
            if(!dq[j-1].empty()){
                int tp=dq[j-1].front();
                //cout<<i<<" "<<j<<" "<<tp<<endl;
                dp[i][j]=b[i]*(-2*b[tp+1])+b[tp+1]*b[tp+1]+b[i]*b[i]+dp[tp][j-1];
                //y=dp[k][j-1]+b[k+1]^2   k=b[i] x=-2*b[k+1]
            }
            while(dq[j].size()>=2){
                int bk=dq[j].back();
                dq[j].pop_back();
                int bk2=dq[j].back();
                if((dp[i][j]+b[i+1]*b[i+1]-dp[bk][j]-b[bk+1]*b[bk+1])*(-2*b[bk+1]+2*b[bk2+1])>(dp[bk][j]+b[bk+1]*b[bk+1]-dp[bk2][j]-b[bk2+1]*b[bk2+1])*(-2*b[i+1]+2*b[bk+1])){
                    continue;
                }
                else {
                    dq[j].push_back(bk);
                    break; 
                }
            }
            dq[j].push_back(i);
            /*for(int k=i-1;k>=0;k--){
                dp[i][j]=min(dp[i][j],dp[k][j-1]+b[k+1]*b[k+1]-2*b[i]*b[k+1]+b[i]*b[i]);
                //y=dp[k][j-1]+b[k+1]^2   K=b[i] x=-2*b[k+1]
            }*/
        }
    }
    /*for(int i=0;i<=cnt;i++){
        for(int j=0;j<=m;j++){
            cout<<dp[i][j]<<" ";
        }
        cout<<endl;
    }*/
    cout<<dp[cnt][m]<<endl;
}

CF111D Petya 染色

特判 m=1

考虑转化 中间的 m-2 列的颜色数量必然是第一列和第 m列共用的颜色数量

用dp或容斥求出恰好用 i 种颜色涂满 n 个格子的方案数(不需要考虑这 i 种颜色是怎样选取的)

枚举共用颜色数量 x ,非共用颜色数量 y

共用颜色选择方案数 C(k,x)

对于第一列 方案数为 C(k-x,y)\times dp_{x+y}

对于第 m 列 方案数为 C(k-x-y,y)\times dp_{x+y} (最后一列的 y 种颜色不能和第一列相同)

中间 m-2 列方案数为 x^{(m-2)\times n}

枚举 x,y ,每次用乘法原理累乘即可

P4381岛屿

一道好题。

考虑每个点向外连一条边,一个连通块内必然有且只有一个环,即形成一个基环树森林。

采用拓扑排序把基环树森林缩成若干个环,在拓扑排序的过程中DP记录每个节点为根节点获得的最大价值(dp值)。此过程类似树型DP。不难发现,每个环对答案的贡献是独立的。

分类讨论:

$2$ 路径经过环上的边,将环翻倍便能在序列上处理 我们要求的答案为 $max(sum_r-sum_l+val_r+val_l)$ $(r-l<sz)

进行常数分离

枚举 r 即可得出 sum_r+val_r ,只要维护r-sz+1r 区间内 val_l-sum_l 最大值即可

使用单调队列可以简单解决问题

最后对于每个联通块相加即可

10.17

ABC223D

一道拓扑排序的魔改题。

很容易想到建图

首先如果原图有环,必然为 -1

跑出来的拓扑序必然是符合题意的,只是我们需要使字典序最小。

我们可以把原先的 queue 换成 priority\ queue ,这样就可以从小到大选择,就可使字典序最小。

10.3学校比赛补题

P1314

一道挺妙的二分

我们通过不知道什么方法可以知道

对于 $check$ 的函数设计,需要用两个前缀和,一个表示前缀符合条件的个数,另一个表示前缀符合条件的 $v_i$ 之和 特别注意,要开 $long \ long$ ,不然会挂得很惨,like this [WA30](https://www.luogu.com.cn/record/59054659) ```cpp #include<bits/stdc++.h> using namespace std; long long n,m,s; long long w[200010],v[200010]; long long l[200010],r[200010]; long long pre[200010],pre2[200010]; long long check(long long id){ for(int i=1;i<=n;i++){ pre[i]=pre[i-1]; pre2[i]=pre2[i-1]; if(w[i]>=id){ pre[i]++; pre2[i]+=v[i]; } } long long x=0; for(int i=1;i<=m;i++){ x+=(pre[r[i]]-pre[l[i]-1])*(pre2[r[i]]-pre2[l[i]-1]); } return x; } int main(){ cin>>n>>m>>s; for(int i=1;i<=n;i++){ cin>>w[i]>>v[i]; } for(int i=1;i<=m;i++){ cin>>l[i]>>r[i]; } //for(int i=1;i<=20;i++)cout<<check(i)<<endl; long long L=0,R=1e6+1; while(L<R){ long long mid=(L+R+1)>>1; if(check(mid)>=s)L=mid; else R=mid-1; } //cout<<check(L)<<endl; long long ans=min(check(L)-s,s-check(L+1)); cout<<ans<<endl; } ``` 其实本题还有一种做法,但由于不太好打,所以不推荐: 即我们可以发现 $|y-s|$ 关于 $W$ 的函数是一个凹函数,故可以使用三分求出极小值,三分的时候可以用斜率进行判断,类似求导的过程。 9.8 [CF242C](https://www.luogu.com.cn/problem/CF242C) 直接能到的就连边跑 $BFS$ 就行 如果只能横竖走,那直接排两次序乱搞 如果能斜着走,那就可以把所有点的坐标 $hash$ 过后存进 $map$ 中去 注意,如果当前的 $mp_i$ 没有元素,那么默认为 $0$ ,所以数组下标要从 $1$ 开始 ```cpp #include<bits/stdc++.h> using namespace std; const int MAXN=4e5+10; struct Pt{ long long r; long long x; }P[MAXN]; struct Node{ long long pos; long long step; }Temp; long long startx,starty,endx,endy; long long cnt=0; vector<Pt> v; long long r[MAXN],X[MAXN],Y[MAXN]; long long startid,endid; long long n; bool cmp(Pt a,Pt b){ if(a.r!=b.r)return a.r<b.r; return a.x<b.x; } vector<long long> Adj[MAXN]; bool vis[MAXN]; map<long long,long long> mp; int BFS(){ vis[startid]=true; Temp.pos=startid; Temp.step=0; queue<Node> q; q.push(Temp); while(!q.empty()){ Node tp=q.front(); q.pop(); if(tp.pos==endid)return tp.step; for(int i=0;i<Adj[tp.pos].size();i++){ int x=Adj[tp.pos][i]; if(!vis[x]){ vis[x]=true; Temp.pos=x; Temp.step=tp.step+1; q.push(Temp); } } } return -1; } long long gethash(long long i,long long j){ return i*1e9+j; } int dx[]={-1,-1,0,1,1,1,0,-1}; int dy[]={0,1,1,1,0,-1,-1,-1}; int main(){ cin>>startx>>starty>>endx>>endy; cin>>n; for(int i=1;i<=n;i++){ cin>>r[i]>>X[i]>>Y[i]; for(int j=X[i];j<=Y[i];j++){ cnt++; P[cnt].r=r[i]; P[cnt].x=j; } } sort(P+1,P+cnt+1,cmp); v.push_back(P[1]); for(int i=2;i<=cnt;i++){ if(P[i].r!=P[i-1].r||P[i].x!=P[i-1].x)v.push_back(P[i]); } for(int i=0;i<v.size();i++){ if(v[i].r==startx&&v[i].x==starty)startid=i+1; if(v[i].r==endx&&v[i].x==endy)endid=i+1; } for(int i=1;i<=v.size();i++){ mp[gethash(v[i-1].r,v[i-1].x)]=i; } for(int i=0;i<v.size();i++){ for(int j=0;j<=7;j++){ int nowx=v[i].r+dx[j]; int nowy=v[i].x+dy[j]; if(mp[gethash(nowx,nowy)])Adj[i+1].push_back(mp[gethash(nowx,nowy)]); } } //cout<<startid<<" "<<endid<<endl; cout<<BFS()<<endl; } ``` 9.7 [CF363D](https://www.luogu.com.cn/problem/CF363D) 二分题,一直在写贪心,遂一直挂着,非常惨 思路,考虑排序,人手上的钱按降序,价钱按升序 二分最长的序列长度 $l$ ,然后匹配,第一个人配第 $l$ 个,第二个人配第 $l-1$ 个,以此类推, $check$ 即可。 第二小问可以对 $q_1$ 到 $q_l$ 求前缀和,减去 $a$ 即可。 9.6 [CF591C](https://www.luogu.com.cn/problem/CF590A) 这道题其实蛮简单的 题目中的操作转化一下其实就变成了找出那些形如 $010101...$ 或 $101010...$,的序列。对于每个这样的序列,我们都可以用 $\lceil n/2 \rceil$ 次操作将它们变成一样的,所以说取最大值即可,即 $\lceil max(r_i-l_i+1)/2 \rceil

对于构造,如果序列长度为奇数,则必然有 a_{l_i-1}=a_{r_i+1} ,则最后对于 l_i\leq j \leq r_i , a_j=a_{l_i-1}a_{r_i-1}

如果是偶数,则前半部分等于 a_{l_i-1} ,后半部分等于 a_{r_i-1}

9.3

CF1057C

一道 dp

考虑实际上他走的路径构成一个DAG(因为他只能从小的节点往大的节点走,大小关系又是确定的),必然可以 dp

设计状态:令 dp_{i,j} 表示到节点 i 获得值为 j 的最小路程。

考虑从小到大处理,一个较大的节点必然可以从一个较小的异色节点转移,且代价为 |x_i-x_j| ,暴力转移即可,时间复杂度 O(n^2k)

#include<bits/stdc++.h>
using namespace std;
const int INF=1e9;
struct Node{
    int id;
    int val;
}A[60];
int n,s,k;
string T;
bool cmp(Node a,Node b){
    return a.val<b.val;
}
int dp[60][2060];
int main(){
    cin>>n>>s>>k;
    for(int i=1;i<=n;i++){
        A[i].id=i;
        cin>>A[i].val;
    }
    cin>>T;
    sort(A+1,A+n+1,cmp);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k+50;j++)dp[i][j]=INF;
    }
    for(int i=1;i<=n;i++){
        int x=A[i].id;
        int v=A[i].val;
        for(int j=0;j<=v;j++)dp[x][j]=abs(x-s);
        for(int j=1;j<i;j++){
            int y=A[j].id;
            if(T[x-1]!=T[y-1]&&v>A[j].val){
                for(int r=v;r<=k+50;r++){
                    dp[x][r]=min(dp[x][r],dp[y][r-v]+abs(y-x));
                }
            }
        }
    }
    int Ans=INF;
    for(int i=1;i<=n;i++){
        for(int j=k;j<=k+50;j++)Ans=min(Ans,dp[i][j]);
    }
    if(Ans==INF)cout<<-1<<endl;
    else cout<<Ans<<endl;
}

9.2

CF486D

考虑没有 d 的限制的情况,我们可以令 dp_i 为这个子树中的最大值,然后用 f_i=\prod\ (f_j+1),其中 ji 的儿子。

d 的限制的情况,对于每个点作为根做 ndp , 注意我们钦定根节点是权值最大的节点(若有多个相同的则应该是编号最大的),这样才能做到不漏

#include<bits/stdc++.h>
using namespace std;
const long long mod=1000000007;
const int MAXN=2010;
int n,d;
long long a[MAXN];
vector<int> Adj[MAXN];
long long Ans=0;
long long dp[MAXN];
bool vis[MAXN];
int st;
void dfs(int u){
    //cout<<u<<endl;
    for(int i=0;i<Adj[u].size();i++){
        int x=Adj[u][i];
        //cout<<"YEAH"<<endl;
        if(vis[x])continue;
        //cout<<"YEAH"<<endl;
        if(a[x]>a[st])continue;
        //cout<<x<<" "<<st<<endl;
        //cout<<a[x]<<" "<<a[st]<<endl;
        //cout<<"YEAH"<<endl;
        if(a[x]==a[st]&&x>st)continue;
        //cout<<"YEAH"<<endl;
        if(a[st]-a[x]>d)continue;
        vis[x]=true;
        dfs(x);
        dp[u]*=(dp[x]+1);
        dp[u]%=mod;
        vis[x]=false;
    }
}
int main(){
    cin>>d>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    int u,v;
    for(int i=1;i<n;i++){
        cin>>u>>v;
        Adj[u].push_back(v);
        Adj[v].push_back(u);
    }
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        for(int j=1;j<=n;j++)dp[j]=1;
        vis[i]=true;
        st=i;
        dfs(i);
        //cout<<dp[st]<<endl;
        //cout<<endl;
        Ans+=dp[st];
        Ans%=mod;
    }
    cout<<Ans<<endl;
}

8.30

难度适中的一道题

算法一

算法二 考虑算法一的第二维实际要开到 $T$ , 故考虑把答案移到第二维,把第二维移到答案,即$dp_{i,j}$ 表示到第 $i$ 个点长度为 $j$ 的最小花费,并记录其前驱,按拓扑序dp即可 8.26 COCI2014-2015 Final B 做法:考虑把相等关系缩点,注意,没有被缩到一个大点的两个小点不代表两个小点不等,可以使用并查集。然后正着扫一遍,再反着扫一遍,可以用dp或者直接搜索。注意暴力dfs可能会爆栈!!记录下来的两个位置若和为$n+1$,则在一条长度为 $n$ 的链上,符合条件。 ``` #include<bits/stdc++.h> using namespace std; const int MAXN=3e5+10; int n,m,v; int a[MAXN],b[MAXN]; char c[MAXN]; int father[MAXN]; int findfather(int x){ int a=x; while(father[x]!=x)x=father[x]; while(father[a]!=a){ int z=a; a=father[a]; father[z]=x; } return x; } void Union(int a,int b){ int faA=findfather(a); int faB=findfather(b); if(faA!=faB)father[faA]=faB; } vector<int> Forest[MAXN]; vector<int> newnode; vector<int> Adj[MAXN]; vector<int> Adj2[MAXN]; int degree[MAXN]; bool vis[MAXN]; int dp[MAXN]; bool used[MAXN]; int Depth[MAXN]; void dfs(int u){ if(Adj[u].size()==0){ //cout<<u<<endl; Depth[u]=1; return; } for(int i=0;i<Adj[u].size();i++){ int x=Adj[u][i]; if(!Depth[x])dfs(x); Depth[u]=max(Depth[u],Depth[x]+1); } } int main(){ //freopen("kovanice.in","r",stdin); //freopen("kovanice.out","w",stdout); //cout<<"YEAH"<<endl; cin>>n>>m>>v; for(int i=1;i<=v;i++){ scanf("%d%c%d",&a[i],&c[i],&b[i]); } for(int i=1;i<=m;i++)father[i]=i; for(int i=1;i<=v;i++){ if(c[i]=='='){ Union(a[i],b[i]); } } for(int i=1;i<=m;i++){ int x=findfather(i); Forest[x].push_back(i); if(!used[x]){ used[x]=true; newnode.push_back(x); } } //cout<<newnode.size()<<endl; //cout<<"YEAH"<<endl; for(int i=1;i<=v;i++){ int faA=findfather(a[i]); int faB=findfather(b[i]); if(c[i]=='<'){ Adj[faA].push_back(faB); Adj2[faB].push_back(faA); degree[faB]++; } if(c[i]=='>'){ Adj[faB].push_back(faA); Adj2[faA].push_back(faB); degree[faA]++; } } for(int i=1;i<=m;i++)dp[i]=1; //cout<<"YEAH"<<endl; for(int i=0;i<newnode.size();i++){ if(!degree[newnode[i]]){ //cout<<newnode[i]<<" "; dfs(newnode[i]); } } //cout<<endl; queue<int> q; for(int i=0;i<newnode.size();i++){ if(!degree[newnode[i]]){ q.push(newnode[i]); } } while(!q.empty()){ int tp=q.front(); q.pop(); for(int i=0;i<Adj[tp].size();i++){ int x=Adj[tp][i]; degree[x]--; if(degree[x]==0){ q.push(x); } dp[x]=max(dp[x],dp[tp]+1); } } //cout<<"YEAH"<<endl; //cout<<"YEAH"<<endl; //for(int i=0;i<newnode.size();i++)cout<<Depth[newnode[i]]<<" "; //cout<<dp[6]<<" "<<dp[7]<<endl; //cout<<endl; for(int i=1;i<=m;i++){ int x=findfather(i); //cout<<x<<endl; if(dp[x]+Depth[x]!=n+1){ printf("?\n"); } else printf("K%d\n",dp[x]); } /*for(int i=0;i<newnode.size();i++){ cout<<newnode[i]<<" "<<dp[newnode[i]]<<endl; }*/ } ``` 8.24 [P7795](https://www.luogu.com.cn/problem/P7795)源自校内模拟赛补题。 思路:考虑二分,显然有单调性,可以把最优性问题转化为可行性问题。 二分平均值 $ave$ ,将原数列 $a$ 的每个值减去 $ave$ 得到数组 $b$ , 当前的 $ave$ 值满足条件当且仅当存在一个长度大于等于 $k$ 的 $b$ 的子序列和大于等于0 考虑构造符合条件的和最大的子序列 $b_l+b_{l+1}+...+b_{r-1}+b_r=pre_r-pre_{l-1}$ ($pre$为前缀和数组) 枚举 $r$ ,对于每个 $r$ ,找范围内最小的 $pre_{l-1}$即可。 时间复杂度 $O(nlogn)

8.13

网络流,最小割变形 题目

拆点的trick

把一个点拆成入点和出点,分别记为 i 号点和 i+n 号 点

连边时连(i->n+i,1),(n+i->i,0),(u+n->v,INF),(v+n->u,INF),(v->u+n,0),(u->v+n,0)

直接跑最小割(最大流)板子即可

注意起点会变成s+n

8.9

CF603A 考虑一个trick,即LIS 可设计出状态,f_{i,0} 表示该位不反转,前面也无反转的最长长度,f_{i,1} 表示该位不反转,但前面有反转的的最长长度,f_{i,2}表示该位反转的情况。分类讨论s_is_{i-1} 是否相同转移即可。注意边界。 可知dp之难点者,在于状态之设计也。代码非常简洁美观,如下

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010;
int n;
string s;
long long dp[MAXN][4];
long long ans=1; 
int main(){
    cin>>n;
    cin>>s;
    dp[0][0]=dp[0][1]=dp[0][2]=1;
    for(int i=1;i<n;i++){
        if(s[i]==s[i-1]){
            dp[i][0]=dp[i-1][0];
            dp[i][1]=max(dp[i-1][1],dp[i-1][2]+1);
            dp[i][2]=max(dp[i-1][0]+1,dp[i-1][2]);
        }
        else {
            dp[i][0]=dp[i-1][0]+1;
            dp[i][1]=max(dp[i-1][1]+1,dp[i-1][2]);
            dp[i][2]=max(dp[i-1][0],dp[i-1][2]+1);
        }
        ans=max(max(max(dp[i][0],dp[i][1]),dp[i][2]),ans);
    }
    cout<<ans<<endl;
} 

8.6

CF621C

基础的概率期望题,但我还是看了一眼题解,挂了两发

坑点: 1 拿钱的时候是两个人一起获得,而非单方面获得

2.注意实数运算(样例水)

ABC212 D

赛场上clf神仙用线段树写出来了,\% \% \% ,而我写自闭了

这里是正解

参考了官方解法

我们先定义数组a,其中当opt_i=2a_i=x_i ,否则a_i=0

考虑前缀和的思想,定义s_i=\sum a_i,每次各数即为

(x_i-s_i)+s_k

显然s_k 为定值,因而我们只要在每次opt_i=1操作时找到最小的(x_i-s_i),考虑使用priority queue 进行维护,注意开long long

代码

#include<bits/stdc++.h>
using namespace std;
priority_queue<long long> q;
int n;
long long sum=0;
int main(){
    cin>>n;
    int opt;
    long long x;
    for(int i=1;i<=n;i++){
        cin>>opt;
        if(opt==1){
            cin>>x;
            q.push(sum-x);
        }
        if(opt==2){
            cin>>x;
            sum+=x;
        }
        if(opt==3){
            long long tp=q.top();
            q.pop();
            long long temp=sum-tp;
            cout<<temp<<endl;
        }
    }
}

7.31

差分约束,注意边的方向,还有没有确定源点时的处理

7.30

欧拉定理:对于任意a\perp n ,a^{\varphi(n)}\equiv 1(mod\ n)

扩展欧拉定理:b\geq \varphi(n) ,a^b \equiv a^{b\%\varphi(n)+\varphi(n)}(mod\ n)

需要注意的是,当 b\leq \varphi(n)时原式不一定成立

7.7

P6218

问题很多

1 1<<n 会爆 long \ long

2

dp[i][0][0]=1;
//dp[i][1][1]=1;
//dp[i][1][i]=1;
//dp[i][0][i-1]=1;

dp 转移的时候注意重复转移和后效性

3 细节问题

for(int k=0;k+num<=cnt/2;k++){//是cnt而不是i 
    ans+=dp[i][0][k];//dp[i][0][k]而不是dp[i][0][k+num] 
}

7.5

P2602 数字计数

ans+=num*Power[i-1];//注意不是ans+=num 

7.3

P3379 LCA板子

1.递推(DP)时注意后效性

2.特判结点0

5.29

P5231

第一道紫题

AC自动机板子,注意在 find 函数中打标记,用-1不用0

3.6

P2979 看了题解

注意:完全背包(顺序)+分类讨论+贪心

2.28

P1725 自己做出

线段树优化DP O(nlogn)

单调队列优化DP O(n)?

12.26

CF7B 自己做出

P3865参考了书

P6278模拟赛补题

算法:树状数组(类似桶) 差分

CF27C

算法:前缀后缀最大值

注意:转移

P4873 补题

线段树优化dp

P3112 补题

状压dp

注意:不要使用某些贪心,虽然好写,但会丢分

P4302

区间dp

P3205

同上

注意转移方程设计

12.25

CF3B debug时参考了题解

注意:1.边界(防止越界) 2.特判

12.20

P4170 自己做出

CF2B 思路看了题解 注意:1.边界

2.矩阵中有0时要特判