星期一:
继续补天梯选拔赛round 2的题:
思路:线性dp加前缀和,dp[ i , j ]表示考虑到前i个中挑k组的最大获利, 最终答案为dp[n,k] , 两重循环跑n和k
代码如下:
ll n;
int m,k;
ll dp[5050][5050],p[5050];
void solve(){
cin >> n >> m >> k;
for(int i=1;i<=n;i++){
cin >> p[i];
p[i]+=p[i-1];
}
memset(dp,-1,sizeof dp);
for(int i=0;i<=n;i++) dp[i][0]=0;
for(int i=m;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j]=dp[i-1][j];
if(dp[i-m][j-1]==-1) continue;
dp[i][j]=max(dp[i-m][j-1]+p[i]-p[i-m],dp[i][j]);
}
}
cout << dp[n][k] << "\n";
stack<int>ans;
int cnt=k;
for(int i=n;i>=m && cnt;i--){
if(dp[i][cnt]==dp[i-m][cnt-1]+p[i]-p[i-m]){
ans.push(i);
i-=m-1,cnt--;
}
}
while(!ans.empty()){
int tmp=ans.top(); ans.pop();
cout << tmp-m+1 << " " << tmp << "\n";
}
}
晚上cf round933 div3,做到了E,优先队列优化线性dp,能赛时做出来我很满意
F题思路比E更简单,没做出来因为时间不够,赛后补了
星期二:
pta上天梯训练赛,手速可以,败在上限
补一题:
为了这题赛时学了个最近公共祖先lca(least common ancestor),结果题解发现用不着,汗流浃背了
思路:每个点往上dfs,d到标记过的点就结束,因为默认返回,所以return 路径*2,记录从外卖站开始的一条最长的路径不返回,所以输出要减一个最长路径,感觉这题要点手法的
代码如下:
const int N=1e6+10;
const int mod=1e9+7;
ll n;
int m,s,ma;
int fa[N],a[N];
bool vi[N];
ll ans;
int dfs(int x,int cnt){
if(x==s || a[x]){
ma=max(a[x]+cnt,ma);
return cnt*2;
}
int res=dfs(fa[x],cnt+1);
a[x]=a[fa[x]]+1;
return res;
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;i++){
int x;cin >> x;
if(x==-1){s=i; continue;}
fa[i]=x;
}
vi[s]=1;
while(m--){
int x;cin >> x;
ans+=dfs(x,0);
cout << ans-ma << "\n";
}
}
贴个最近公共祖先LCA板子: 时间复杂度 O( (n+m) logn )
const int N=1e6+10;
const int mod=1e9+7;
ll n;
int m;
vector<int>ve[N];
int dep[N],fa[N][20]; //1<<20 近似 1e6
void dfs(int u,int f){ //O(nlogn)
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<=19;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:ve[u])
if(v!=f) dfs(v,u);
}
int lca(int u,int v){ //O(logn)
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return v;
for(int i=19;i>=0;i--)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
星期三:
补cf round933 G题:
题意:无向图,无自环,无重边,两点间的边以一种颜色标注,给定起始点和终点,输出路径最少经过的颜色数
思路:学到了个新东西,建立虚点,代替原来的边,将图进行转化,每种颜色建一个虚点,连接此颜色边所连的点,虚点到点的单向边边权为0,点到虚点单向边边权为1,这样同颜色边所连接的点就能以 1 的代价移动,对应题意
如例题图,建立虚点后的图是这样的:
图建好后跑个dij就行(01bfs也可以)
代码如下:
const int N=1e6+10;
const int mod=1e9+7;
ll n;
int m,b,e;
vector<PII>ve[N];
ll dis[N];
bool vi[N];
void dij(int s){
priority_queue<PII,vector<PII>,greater<PII>>pq;
for(int i=1;i<=n+m+2;i++) dis[i]=1e18;
dis[s]=0;
pq.push({0,s});
while(pq.size()){
int t=pq.top().second,d=pq.top().first; pq.pop();
if(vi[t]) continue;
vi[t]=1;
for(auto [x,y]:ve[t]){
if(d+y<dis[x]){
dis[x]=d+y;
if(x==e) return ;
pq.push({dis[x],x});
}
}
}
}
void solve(){
cin >> n >> m;
map<int,int>mp;
int sp=1;
for(int i=1;i<=m;i++){
int u,v,c;
cin >> u >> v >> c;
if(mp[c]==0) mp[c]=sp++;
ve[n+mp[c]].push_back({u,0});
ve[n+mp[c]].push_back({v,0});
ve[u].push_back({n+mp[c],1});
ve[v].push_back({n+mp[c],1});
}
cin >> b >> e;
dij(b);
cout << dis[e] << "\n";
for(int i=1;i<=n+m+2;i++) ve[i].clear(),vi[i]=0;
}
入门 数位dp b站传送门
代码如下:
ll n;
int f[12][10],a[12];
void init(){
for(int i=0;i<=9;i++) f[1][i]=1;
for(int i=1;i<=11;i++){ //可以涵盖9.9e10范围的数
for(int j=0;j<=9;j++)
for(int k=j;k<=9;k++)
f[i][j]+=f[i-1][k];
}
}
ll dp(ll n){
if(!n) return 1;
int cnt=0,now=0,last=0;
ll res=0;
while(n) a[++cnt]=n%10,n/=10;
for(int i=cnt;i>=1;i--){
now=a[i];
for(int j=last;j<now;j++)
res+=f[i][j];
if(now<last) break;
last=now;
if(i==1) res++;
}
return res;
}
void solve(){
init();
int x,y;
while(cin >> x >> y){
cout << dp(y)-dp(x-1) << endl;
}
}
做题做到个询问区间最大值,死活想不出来,原来是又得学新算法了
ST表(RMO)板子: O(nlogn)初始化,O(1)查询
const int N=2e6+10;
const int mod=1e9+7;
ll n;
int t,q;
int a[N];
ll f[N][20];
void init(){
for(int i=1;i<=n;i++) f[i][0]=a[i];
for(int j=1;j<20;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
}
ll que(int l,int r){
int k=log2(r-l+1);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
星期四:
数位dp windy数 洛谷传送门
思路:代码的形式和之前的不降数很像,先预处理,然后前缀和计算
代码如下:
ll n;
int a[12];
ll f[12][20]; //位数为i,最高位为j含有的windy数
void init(){
for(int i=0;i<=9;i++) f[1][i]=1;
for(int i=2;i<12;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
}
ll dp(int x){
if(!x) return 0;
int cnt=0,last=-2,now=0;ll res=0;
while(x) a[++cnt]=x%10,x/=10;
for(int i=cnt;i>=1;i--){ //位数为cnt的数对答案的贡献
now=a[i];
for(int j=(i==cnt);j<now;j++) //i==cnt时,j从1开始
if(abs(j-last)>=2) res+=f[i][j];
if(abs(now-last)<2) break;
last=now;
if(i==1) res++;
}
for(int i=1;i<cnt;i++) //位数小于cnt的数对答案的贡献
for(int j=1;j<=9;j++) res+=f[i][j]; //位数小于cnt时最高位不能为0
return res;
}
void solve(){
init();
int x,y;cin >> x >> y;
cout << dp(y)-dp(x-1);
}
数位dp 度的数量 b站传送门
思路: 用数组模拟b进制,满足条件的数即为恰好有k个数位为1的数,需预处理组合数
代码如下:
ll n;
int k,b;
int a[33];
int f[33][33];
void init(){
for(int i=0;i<33;i++) f[i][0]=1;
for(int i=1;i<33;i++)
for(int j=1;j<=i;j++)
f[i][j]=f[i-1][j]+f[i-1][j-1];
}
ll dp(int x){
if(!x) return 0;
int cnt=0,last=0;ll res=0;
while(x) a[++cnt]=x%b,x/=b;
for(int i=cnt;i>=1;i--){
if(a[i]){
res+=f[i-1][k-last];
if(a[i]==1){
last++;
if(last>k) break;
}else{
if(k-last-1>=0) res+=f[i-1][k-last-1];
break;
}
}
if(i==1 && last==k) res++;
}
return res;
}
void solve(){
init();
int x,y; cin >> x >> y;
cin >> k >> b;
cout << dp(y)-dp(x-1);
}
数位dp 洛谷传送门
经过三道数位dp入门后,自己在洛谷上找的一道,攻克失败
先看初始化,f [ i , j ]还是表示位数为i,最高位为j的所有数的数字和
我自己写的初始化函数:
ll f[20][10],a[20];
void init(){
for(int i=0;i<=9;i++) f[1][i]=i;
for(int i=2;i<32;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
f[i][j]+=f[i-1][k]+j,f[i][j]%=mod;
}
错在哪呢,f[3,1]应该是1000,我这函数初始化后只有910,原因在于只加了十次百位的1,而正确的应该加上从00到99一百次1,正确初始化函数如下:
ll f[20][10],a[20],p10[20];
void init(){
p10[0]=1; //意为pow(10,i)
for(int i=1;i<20;i++) p10[i]=p10[i-1]*10,p10[i]%=mod;
for(int i=0;i<=9;i++) f[1][i]=i;
for(int i=2;i<20;i++){
for(int j=0;j<=9;j++){
f[i][j]=j*p10[i-1];
for(int k=0;k<=9;k++)
f[i][j]+=f[i-1][k],f[i][j]%=mod;
}
}
}
星期五:
经过上午及一点点中午的攻克,上题已成功拿下
放代码:
ll n;
ll f[20][10],a[20];
unsigned long long p10[20]; //不用ull数据范围也够
void init(){
p10[0]=1;
for(int i=1;i<20;i++) p10[i]=p10[i-1]*10; //这里不模mod,否则下面的取模操作会出错
for(int i=0;i<=9;i++) f[1][i]=i;
for(int i=2;i<20;i++){
for(int j=0;j<=9;j++){
f[i][j]=j*p10[i-1],f[i][j]%=mod;
for(int k=0;k<=9;k++)
f[i][j]+=f[i-1][k],f[i][j]%=mod;
}
}
}
ll dp(ll x){ //这里dp不能用无符号型,因为下面的相减操作可能出现负数
if(!x) return 0;
int cnt=0,now=0;ll res=0,tmp=x;
while(x) a[++cnt]=x%10,x/=10;
for(int i=cnt;i>=1;i--){ //位数等于cnt的数对res的贡献
now=a[i];
for(int j=(i==cnt);j<now;j++) //i为cnt时,j从1开始,后面可从0开始
res+=f[i][j],res%=mod;
res+=now*(tmp%p10[i-1]+1)%mod,res%=mod; //固定高位,加上高位数对res的贡献
}
for(int i=1;i<cnt;i++) //位数小于cnt的数对res的贡献
for(int j=1;j<=9;j++)
res+=f[i][j],res%=mod;
return res;
}
void solve(){
init();
int t; cin >> t;
while(t--){
ll l,r; cin >> l >> r;
cout << ((dp(r)-dp(l-1))%mod+mod)%mod << "\n"; //模后相减可能为负,需再+mod)%mod处理
}
}
之前卡我的cf round928的C也可以酱紫秒了,虽然其正解是前缀和
星期六:
成信大二次游,打了个他们的天梯校赛,补题链接也不给个
晚上cf round934 div2又掉分,连续两晚咔咔掉
C题开始贪心思路错了,后面又漏了个break,整半天,罚时寄完了
周日:
简单vp下昨晚的abc 345,C题也是思路没完善导致wa了四发,止步于D
补了下arc173的A,也算数位dp: ATC传送门
思路:应该有其他思路,但我是当数位dp加二分做的,二分的时候注意dp结果等于k了也不一定是答案,可能是大于答案的值,所以记录一下再往下分
有一个比较坑的点就是它没说第1e12个neq数大概会是多大,所以二分的上界不好确定,之前用ll的1e18就wa了很多发,得用ull的1e19才行
代码如下:
ll n;
ll f[20][10],a[20];
void init(){
for(int i=0;i<=9;i++) f[1][i]=1;
for(int i=2;i<20;i++){
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
if(j!=k) f[i][j]+=f[i-1][k];
}
}
ll dp(ll x){
int cnt=0;ll res=0;
while(x) a[++cnt]=x%10,x/=10;
int last=0;
for(int i=cnt;i;i--){
int now=a[i];
for(int j=(i==cnt);j<now;j++)
if(j!=last) res+=f[i][j];
if(now==last) break;
last=now;
if(i==1) res++;
}
for(int i=1;i<cnt;i++)
for(int j=1;j<=9;j++)
res+=f[i][j];
return res;
}
void solve(){
init();
int t; cin >> t;
while(t--){
ll k; cin >> k;
if(k<=10){cout << k << "\n"; continue;}
ll l=12,r=1e19,ans=0;
while(l<=r){
ll mid=l+(r-l)/2;
ll res=dp(mid);
if(res>k) r=mid-1;
else if(res<k) l=mid+1;
else ans=mid,r=mid-1; //记录后继续往下分
}
cout << ans << "\n";
}
}
尝试补了下ABC345的D,dfs暴搜,发现自己的dfs暴搜很弱,然后去练了下基础的八皇后问题
贴下代码:
ll n;
ll ans;
int a[20],c[20],p[40],q[40];
void print(){
if(ans>3) return ;
for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];
}
void dfs(int i){
if(i>n){ans++; print(); return ;}
for(int j=1;j<=n;j++){
if(c[j] || p[i+j] || q[i-j+n]) continue;
c[j]=p[i+j]=q[i-j+n]=1; //标记列和俩对角线
a[i]=j; //填入
dfs(i+1);
c[j]=p[i+j]=q[i-j+n]=0;
}
}
void solve(){
cin >> n;
dfs(1);
cout << ans;
}
最近公共祖先LCA tarjan算法: 复杂度O(n+m)
const int N=2e6+10;
const int mod=1e9+7;
ll n;
int m,s;
vector<int>e[N];
vector<PII>que[N];
int fa[N],ans[N];
bool vi[N];
int fnd(int x){
return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void tarjan(int u){
vi[u]=1;
for(auto v:e[u]){
if(vi[v]) continue;
tarjan(v); //深搜子节点
fa[v]=u; //出来后连上关系
}
for(auto [v,i]:que[u]){ //离开前遍历询问
if(vi[v]) ans[i]=fnd(v);
}
}
void solve(){
cin >> n >> m >> s;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=m;i++){
int a,b; cin >> a >> b;
que[a].push_back({b,i});
que[b].push_back({a,i});
}
tarjan(s);
for(int i=1;i<=m;i++) cout << ans[i] << "\n";
}