传送门
维护前缀和
找前面最小的
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
const int INF=2147483647;
int n,a[N],p[N];
int main(){
n=in;
for(int i=1;i<=n;++i) a[i]=a[i-1]+in;
int tmp=INF,id=0;
for(int i=1;i<=n;++i){
p[i]=id;
if(a[i]<tmp) id=i,tmp=a[i];
}
tmp=-INF;
for(int i=1;i<=n;++i) tmp=max(tmp,a[i]-a[p[i]]);
printf("%d\n",tmp);
return 0;
}
交上去一波发现80pts
究其原因:只关注前面最值不够
考虑贪心
维护前缀和,但是前缀和不优秀就直接取此数
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
const int INF=2147483647;
int n,a[N],p[N],ans=-INF;
int main(){
n=in;
for(int i=1;i<=n;++i) a[i]=in;
for(int i=1;i<=n;++i){
p[i]=max(p[i-1]+a[i],a[i]);
ans=max(ans,p[i]);
}
cout<<ans<<endl;
return 0;
}
TOP
Portkey
棋盘型DP
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=1e3+5,M=1e5+5,mod=1e5+3;
int n,m,f[N][N];
bool b[N][N];
int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
int x=in,y=in;
b[x][y]=true;
}
f[1][1]=1;
for(int x=1;x<=n;++x)
for(int y=1;y<=n;++y){
if(b[x][y]||(x==1&&y==1)) continue;
f[x][y]=add(f[x-1][y],f[x][y-1]);
}
printf("%d\n",f[n][n]);
return 0;
}
TOP
Portkey
IOI1994?
就这就这?
好像是第一道看的DP题
(当然当时很菜)
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=1e3+5;
int n,a[N][N],f[N][N],ans=-2147483647;
int main(){
n=in;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
a[i][j]=in;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j];
}
for(int i=1;i<=n;++i)
ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}
TOP
Portkey
LCS板题
考虑维护 f [ i ] [ j ] f[i][j] f[i][j]表示第一个数列第i位前和第二个数列第j位前的LCS
菜至极
只会 n 2 n^2 n2
考虑转化
这是个排列,就可以搞事情
把一个数列的数变成下标,相应的替换另一个数列里的数
这样不会改变两个数列LCS的本质
转化后发现只需要求另一个数列的LIS(最长上升子序列)了
LIS当然用单调队列优化
优秀✌️
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=1e5+5;
int n,a[N],b[N],q[N],len=1;
int main(){
n=in;
for(int i=1;i<=n;++i) a[in]=i;
for(int j=1;j<=n;++j) b[j]=a[in];
q[1]=b[1];
for(int i=1;i<=n;++i){
if(b[i]>q[len]){q[++len]=b[i];continue;}
int pos=lower_bound(q+1,q+len+1,b[i])-q;
q[pos]=min(q[pos],b[i]);
}
printf("%d\n",len);
return 0;
}
TOP
Portkey
本来今天排到一道数位DP的
但是数位DP我自己准备了好多题
就咕了
过两天做专题
NOIP做过
双倍经验
双倍经验个鬼
后面50pts卡 n 2 n^2 n2
二分图呗
二分图个鬼
人家求的是航道不交叉qwq
发现:
两条航线起点终点都是小于或大于,航线不相交
对起点排序
求终点的最长不下降序列
单调队列优化
有点淦的是
不知道为什么记录单调队列长度siz
的历史最大值就不对qwq
求巨佬解释
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
int n,f[N],q[N],siz=1,ans;
struct node{
int u,v;
}p[N];
bool cmp(const node a,const node b){
return a.u<b.u;
}
int main(){
n=in;
for(int i=1;i<=n;++i) p[i].u=in,p[i].v=in;
sort(p+1,p+n+1,cmp);
q[1]=p[1].v;
for(int i=2;i<=n;++i){
if(p[i].v>q[siz]){q[++siz]=p[i].v;continue;}
int pos=lower_bound(q+1,q+siz+1,p[i].v)-q;
q[pos]=min(q[pos],p[i].v);
}
printf("%d\n",siz);
return 0;
}
TOP
Portkey
单源单汇就是最长路
多源单汇就是反一下最长路
多源多汇就是拓扑排序
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
int n,m,deg[N],f[N];
vector<int>G[N];
void topo(){
queue<int>q;
for(int i=1;i<=n;++i) if(!deg[i]) q.push(i),f[i]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int e=0;e<G[u].size();++e){
int v=G[u][e];
--deg[v];
f[v]=max(f[v],f[u]+1);
if(!deg[v]) q.push(v);
}
}
return;
}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
int u=in,v=in;
G[u].push_back(v);
++deg[v];
}
topo();
for(int i=1;i<=n;++i) printf("%d\n",f[i]);
return 0;
}
TOP
Portkey
考察最后图的性质:DAG
联想到拓排
拓排要求DAG,如果在排的时候强行整成DAG呢
是可以的
于是按有向边拓排
遇到无向边将它改造为从这个点出发的有向边
一通暴搞下来得到的就是DAG
题目的
数据保证一开始就有的单向道路中
就很优秀
而且保证有解
#include
using namespace std;
#define in Read()
#define int long long
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
int n,m1,m2;
int tot,first[N],nxt[N],aim[N],wei[N],ori[N];
int deg[N];
queue<int>q;
void ljb(int u,int v,int w){
++tot;
nxt[tot]=first[u];
first[u]=tot;
aim[tot]=v;
ori[tot]=u;
wei[tot]=w;
return;
}
signed main(){
// freopen("IN.in","r",stdin);
// freopen("out.out","w",stdout);
n=in,m1=in,m2=in;
for(int i=1;i<=m1;++i){
int u=in,v=in;
ljb(u,v,0);
++deg[v];
}
for(int i=1;i<=n;++i) if(!deg[i]) q.push(i);
if(!(tot&1)) ++tot;
for(int i=1;i<=m2;++i){
int u=in,v=in;
ljb(u,v,1);
ljb(v,u,1);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int e=first[u];e;e=nxt[e])
if(!wei[e]){
int v=aim[e];
--deg[v];
if(!deg[v]) q.push(v);
}
for(int e=first[u];e;e=nxt[e])
if(wei[e]==1) wei[e^1]=2;
}
for(int i=1;i<=n;++i) if(deg[i]){puts("-1");return 0;}
bool flag=false;
for(int i=1;i<=tot;++i)
if(wei[i]==1)
printf("%lld %lld\n",ori[i],aim[i]),flag=true;
if(!flag) puts("-1");
return 0;
}
这第10个点。。。
invalid answer
top
Portkey
不科学!
他没说食物链是随便一条链还是入度0
到出度0
的链!
设入度0
的点有 x x x个,总共 n n n个点
那么从每个入度进去遍历整张图 O ( x ( n − x ) ) O(x(n-x)) O(x(n−x))
可能T掉
考虑记搜
记录每个点上能到达出度0
点的个数
这个值是一定的,可以记搜
优化至 O ( n ) O(n) O(n)
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
int n,m;
int tot,first[N],nxt[N],aim[N];
int indeg[N],outdeg[N],f[N];
void ljb(int u,int v){
++tot;
nxt[tot]=first[u];
first[u]=tot;
aim[tot]=v;
return;
}
int DFS(int u){
if(f[u]) return f[u];
if(!outdeg[u]) return 1;
int ans=0;
for(int e=first[u];e;e=nxt[e]){
int v=aim[e];
ans+=DFS(v);
}
return f[u]=ans;
}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
int x=in,y=in;
ljb(x,y);
++indeg[y];
++outdeg[x];
}
int ans=0;
for(int i=1;i<=n;++i)
if(!indeg[i]&&outdeg[i])
ans+=DFS(i);
printf("%d\n",ans);
return 0;
}
Portkey
双倍经验
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=1e6+5;
const int mod=80112002;
int n,m;
int tot,first[N],nxt[N],aim[N];
int indeg[N],outdeg[N],f[N];
int add(int a,int b){return a+b>mod?a+b-mod:a+b;}
void ljb(int u,int v){
++tot;
nxt[tot]=first[u];
first[u]=tot;
aim[tot]=v;
return;
}
int DFS(int u){
if(f[u]) return f[u];
if(!outdeg[u]) return 1;
int ans=0;
for(int e=first[u];e;e=nxt[e]){
int v=aim[e];
ans=add(ans,DFS(v));
}
return f[u]=ans;
}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
int x=in,y=in;
ljb(x,y);
++indeg[y];
++outdeg[x];
}
int ans=0;
for(int i=1;i<=n;++i)
if(!indeg[i]&&outdeg[i])
ans=add(ans,DFS(i));
printf("%d\n",ans);
return 0;
}
Top
Portkey
期望概率DP
考虑
f i = ∑ ( p [ i → j ] f [ j ] + w [ i → j ] ) f_i=\sum (p[i\to j]f[j]+w[i\to j]) fi=∑(p[i→j]f[j]+w[i→j])
对于一个点,从它的所有终点走过来
终点期望有累加,边权要考虑
f u = ∑ u → v ( f v + w e ) p e f_u=\sum_{u\to v} (f_v+w_e)p_e fu=u→v∑(fv+we)pe
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=2e5+5;
int n,m;
int tot,first[N],nxt[N],aim[N],wei[N];
double f[N];
int ind[N],siz[N];
void ljb(int u,int v,int w){
++tot;
nxt[tot]=first[u];
first[u]=tot;
aim[tot]=v;
wei[tot]=w;
return;
}
void topo(){
queue<int>q;
q.push(n);
while(!q.empty()){
int u=q.front();q.pop();
for(int e=first[u];e;e=nxt[e]){
int v=aim[e];
f[v]+=(f[u]+1.0*wei[e])/siz[v];
if(!(--ind[v])) q.push(v);
}
}
return;
}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
int u=in,v=in,w=in;
ljb(v,u,w);
++ind[u];
++siz[u];
}
topo();
printf("%.2lf\n",f[1]);
return 0;
}
TOP
Portkey
2 × 5 = 10 ; x ⋅ 0 = 0 2\times 5=10;x\cdot 0=0 2×5=10;x⋅0=0
情况1:统计2,5个数, a n s = min ( 2 n , n , 5 n , n ) ans=\min(2_{n,n},5_{n,n}) ans=min(2n,n,5n,n);
情况2:找到0的路径,答案恒为1。
a n s = min ( 1 , a n s ) ans=\min(1,ans) ans=min(1,ans)
输出路径考虑递归
情况2直接干
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=1e3+5,INF=2147483647;
int n,a[N][N][2],f[N][N][2],ans,_x,t;
void print(int x,int y,int k){
if(x==1&&y==1){putchar(k?'D':'R');return;}
if(x==1) print(x,y-1,0);
else if(y==1) print(x-1,y,1);
else if(f[x][y][t]==f[x][y-1][t]+a[x][y][t]) print(x,y-1,0);
else print(x-1,y,1);
if(x!=n||y!=n){putchar(k?'D':'R');return;}
}
int main(){
n=in;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
int x=in;
if(!x) _x=i;
else{
while(!(x&1)) ++a[i][j][0],x>>=1;
while(!(x%5)) ++a[i][j][1],x/=5;
}
}
for(int i=1;i<=n;++i)
f[0][i][0]=f[0][i][1]=f[i][0][0]=f[i][0][1]=INF;
f[1][1][0]=a[1][1][0];
f[1][1][1]=a[1][1][1];
for(int k=0;k<=1;++k)
for(int i=1;i<=n;++i)
for(int j=(i==1?2:1);j<=n;++j)
f[i][j][k]=min(f[i-1][j][k],f[i][j-1][k])+a[i][j][k];
ans=min(f[n][n][0],f[n][n][1]);
if(_x&&ans>1){
puts("1");
for(int i=1;i<_x;++i) putchar('D');
for(int i=1;i<n;++i) putchar('R');
for(int i=_x;i<n;++i) putchar('D');
}else{
printf("%d\n",ans);
t=!(f[n][n][0]<f[n][n][1]);
print(n,n,0);
}
puts("");
return 0;
}
Top
Portkey
500 500 500的数据范围就很奇怪
大概是个三次算法
一看题,像个背包
n n n种物品,每种无限个,两种体积:
第一种每个物品体积1,容积 m m m,需完全;
第二种每个物品体积 a i a_i ai,容积 b b b,不需完全
O ( n m b ) O(nmb) O(nmb)
注意累计方案数和求最大价值有区别
最大价值是求max,方案数是累加
最终输出的不是出错b个的,而是出错b个以下的
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=505;
int n,m,b,a[N],mod;
int f[N][N];
int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int main(){
n=in,m=in,b=in,mod=in;
for(int i=1;i<=n;++i) a[i]=in;
f[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
for(int k=a[i];k<=b;++k)
f[j][k]=add(f[j][k],f[j-1][k-a[i]]);
int ans=0;
for(int i=0;i<=b;++i) ans=add(ans,f[m][i]);
printf("%d\n",ans);
return 0;
}
TOP
Portkey
倒着加
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=1e5+5;
int n,m,sum[N],f[N],cnt=1;
struct Data{
int p,t;
}d[N];
bool cmp(const Data &u,const Data &v){return u.p>v.p;}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
d[i].p=in,d[i].t=in;
++sum[d[i].p];
}
sort(d+1,d+m+1,cmp);
for(int i=n;i>=1;--i){
if(!sum[i]) f[i]=f[i+1]+1;
else for(int j=1;j<=sum[i];++j){
if(f[i]<f[i+d[cnt].t])
f[i]=f[i+d[cnt].t];
++cnt;
}
}
printf("%d\n",f[1]);
return 0;
}
Portkey
两个东西
考虑 f [ i ] [ j ] f[i][j] f[i][j]直接递推
注意预处理:全是空格的情况
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=3e3+5;
char a[N],b[N];
int k,la,lb,f[N][N];
int main(){
scanf("%s%s",a+1,b+1);
k=in;
la=strlen(a+1),lb=strlen(b+1);
for(int i=1;i<=la;++i) f[i][0]=f[i-1][0]+k;
for(int j=1;j<=lb;++j) f[0][j]=f[0][j-1]+k;
for(int i=1;i<=la;++i)
for(int j=1;j<=lb;++j){
f[i][j]=min(min(f[i-1][j]+k,f[i][j-1]+k),f[i-1][j-1]+abs(a[i]-b[j]));
}
printf("%d\n",f[la][lb]);
return 0;
}
Portkey
f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示第 i i i小时,睡了 j j j小时,现在在/不在睡觉的最优解
第一遍:前天晚上没睡着
f [ i ] [ j ] [ 0 ] = max ( f [ i − 1 ] [ j ] [ 0 ] , f [ i − 1 ] [ j ] [ 1 ] ) f [ i ] [ j ] [ 1 ] = max ( f [ i − 1 ] [ j − 1 ] [ 0 ] , f [ i − 1 ] [ j − 1 ] [ 1 ] + u [ i ] ) \begin{aligned} &f[i][j][0]=\max(f[i-1][j][0],f[i-1][j][1])\\ &f[i][j][1]=\max(f[i-1][j-1][0],f[i-1][j-1][1]+u[i]) \end{aligned} f[i][j][0]=max(f[i−1][j][0],f[i−1][j][1])f[i][j][1]=max(f[i−1][j−1][0],f[i−1][j−1][1]+u[i])
边界为0
a n s 1 = max ( f [ n ] [ b ] [ 0 ] , f [ n ] [ b ] [ 1 ] ) ans_1=\max(f[n][b][0],f[n][b][1]) ans1=max(f[n][b][0],f[n][b][1])
第二遍:前天晚上睡着了
f [ 1 ] [ 1 ] [ 1 ] = u [ 1 ] f[1][1][1]=u[1] f[1][1][1]=u[1]
再做一遍DP
a n s = max ( a n s 1 , f [ n ] [ b ] [ 1 ] ) ans=\max(ans_1,f[n][b][1]) ans=max(ans1,f[n][b][1])
#include
using namespace std;
#define in Read()
int in{
int i=0,f=1;char ch=0;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int N=5e3+5;
int T,n,b,u[N],f[N][N][2],ans;
void DP(){
for(int i=1;i<=n;++i)
for(int j=1;j<=b;++j){
f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]);
if(j!=1)
f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+u[i]);
}
return;
}
void solve(){
n=in,b=in;
for(int i=1;i<=n;++i) u[i]=in;
DP();
ans=max(f[n][b][0],f[n][b][1]);
f[1][1][1]=u[1];
DP();
ans=max(ans,f[n][b][1]);
printf("%d\n",ans);
return;
}
int main(){
T=in;
while(T--) solve();
return 0;
}