口胡(然而有代码)<第二章>
为提高阅读质量防止博主更博时博客炸掉,所以这里只放 \(T_{51}\sim T_{100}\)
前情回顾:口胡(然而有代码)<第一章>
宗旨:把 AFO 之前口胡的题放这里,一句话题解。
题目计数:\(100\)。
悬线法。
可以去这里学习,因为时间有限这里就不讲了。
利用二维前缀和和悬线法的 dp 思想可以做到 \(\mathcal O(n^2)\)。
有限制的背包问题,感觉有蹊跷一定要排序,因为排完序一定不会错!
由于我很傻不会继承选哪些点,所以写的 \(\mathcal O(n\log n+n^2\max d)\)。
#include"iostream"
#include"cstdio"
#include"algorithm"
using namespace std;
int n;
struct node
{
int t,d,c,id;
}a[105];
int dp[2005];
int maxn=0,ans=0,num;
int rt[2005][105];
int cnt=0;
bool cmp(node n,node m){return n.d<m.d;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].t,&a[i].d,&a[i].c),maxn=max(maxn,a[i].d);
for(int i=1;i<=n;i++) a[i].id=i;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
for(int j=a[i].d-1;j>=a[i].t;j--)
{
if(dp[j-a[i].t]+a[i].c>dp[j])
{
dp[j]=dp[j-a[i].t]+a[i].c;
for(int k=1;k<=n;k++) rt[j][k]=rt[j-a[i].t][k];
rt[j][i]=1;
}
}
}
for(int i=1;i<maxn;i++) if(dp[i]>ans) ans=dp[i],num=i;
printf("%d\n",ans);
for(int i=1;i<=n;i++) if(rt[num][i]) cnt++;
printf("%d\n",cnt);
for(int i=1;i<=n;i++) if(rt[num][i]) printf("%d ",a[i].id);
puts("");
return 0;
}
\(53.\) P1607 [USACO09FEB]Fair Shuttle G
很套路(?)的一道贪心,即按结束位置排序然后贪心上最多的牛。
为什么按结束位置排序?
因为会留下最多的上牛机会。
比较经典的贪心。
麻烦在有好多种情况所以我就不写了。
这篇题解[Link]写得很清楚,去那里看吧。
我太懒了啊......
\(55.\) P4084 [USACO17DEC]Barn Painting G
一道不错的树形 \(dp\)。
可以用结合律推一推式子,比如可以得到:
然后处理一下特殊情况即可。
复杂度是 \(\mathcal O(n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define read(x) scanf("%d",&x)
#define MOD 1000000007
#define ll long long
int n,m;
int l,r;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
ll dp[MAXN][5];
int rt[MAXN],top=0;
int lim[MAXN],op[MAXN][5];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int cur,int fa)
{
op[cur][1]=op[cur][2]=op[cur][3]=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) op[cur][lim[j]]=1;
else op[cur][lim[j]]=1,dfs(j,cur);
}
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j!=fa) rt[++top]=j;
}
if(!lim[cur])
{
if(!op[cur][1])
{
ll now=1ll;
for(int i=1;i<=top;i++)
{
now=(dp[rt[i]][2]+dp[rt[i]][3])%MOD*now%MOD;
}
dp[cur][1]=now%MOD;
}
else dp[cur][1]=0;
if(!op[cur][2])
{
ll now=1ll;
for(int i=1;i<=top;i++)
{
now=(dp[rt[i]][1]+dp[rt[i]][3])%MOD*now%MOD;
}
dp[cur][2]=now%MOD;
}
else dp[cur][2]=0;
if(!op[cur][3])
{
ll now=1ll;
for(int i=1;i<=top;i++)
{
now=(dp[rt[i]][1]+dp[rt[i]][2])%MOD*now%MOD;
}
dp[cur][3]=now%MOD;
}
else dp[cur][3]=0;
}
else
{
if(lim[cur]==1)
{
ll now=1ll;
for(int i=1;i<=top;i++)
{
now=(dp[rt[i]][2]+dp[rt[i]][3])%MOD*now%MOD;
}
dp[cur][1]=now%MOD;
dp[cur][2]=dp[cur][3]=0;
}
if(lim[cur]==2)
{
ll now=1ll;
for(int i=1;i<=top;i++)
{
now=(dp[rt[i]][1]+dp[rt[i]][3])%MOD*now%MOD;
}
dp[cur][2]=now%MOD;
dp[cur][1]=dp[cur][3]=0;
}
if(lim[cur]==3)
{
ll now=1ll;
for(int i=1;i<=top;i++)
{
now=(dp[rt[i]][1]+dp[rt[i]][2])%MOD*now%MOD;
}
dp[cur][3]=now%MOD;
dp[cur][1]=dp[cur][2]=0;
}
}
top=0;
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<n;i++) read(l),read(r),add(l,r),add(r,l);
for(int i=1;i<=m;i++) read(l),read(r),lim[l]=r;
dfs(1,0);
printf("%lld\n",(dp[1][1]+dp[1][2]+dp[1][3])%MOD);
return 0;
}
\(56.\) P3668 [USACO17OPEN]Modern Art 2 G
观察一下合法情况都是先开始后结束,和栈一样,所以用栈维护一下即可。
复杂度是 \(\mathcal O(n)\)。
恭喜半退役选手 tlx 读错题目!
所以直接主席树即可。
复杂度是 \(\mathcal O((n+m)\log n)\) 。
先预处理出每个状态下的最短路和每个最短路经过节点的情况,我会做到 \(\mathcal O(nm^2\log m)\)。
然后设 \(dp_{i,j}\) 为第 \(i\) 天走第 \(j\) 天最短路是的最小路费,那么:
然后就是一个 \(\mathcal O(nm^2\log m+nm)\) 的实现了。
\(59.\) P2950 [USACO09OPEN]Bovine Embroidery G
考虑求出交点,然后把圆从某个点展开,离散化一下,就是区间交集个数总和了!
可以用树状数组或平衡树维护,时间复杂度是 \(\mathcal O(n\log n)\)。
练一下单调队列优化 \(dp\) ,这里要留一个单调队列头权值为 \(0\) 做辅助。
时间复杂度是 \(\mathcal O(n+k)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
#define ll long long
int n,k;
int a[MAXN];
int head=1,tail=1;//head=1留白一个0
ll q[MAXN];
int loc[MAXN];
ll dp[MAXN];
ll sum=0;
ll ans=1ll<<62;
int main()
{
read(n),read(k);
for(int i=1;i<=n;i++)
{
read(a[i]);
sum=sum+(ll)a[i];
dp[i]=q[tail]+a[i];
while(head>tail&&q[head]>=dp[i]) head--;
q[++head]=dp[i],loc[head]=i;
if(loc[tail]<i-k) tail++;
}
for(int i=n;i>=n-k;i--) ans=min(ans,dp[i]);
printf("%lld\n",sum-ans);
return 0;
}
\(61.\) P2216 [HAOI2007]理想的正方形
还是单调队列问题。
单调队列套单调队列。
我们先维护一下从某个点出发的 \(1 \times n\) 得矩形的最大值/最小值。
然后在更新最值正方形的时候就可用 \(1\times n\) 的最值来代表一条线了。
时间复杂度是 \(\mathcal O(ab)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
#include"cstring"
using namespace std;
#define ll long long
#define MAXN 1005
int a,b,n;
ll q1[MAXN],loc1[MAXN];
ll q2[MAXN],loc2[MAXN];
ll x[MAXN][MAXN];
int head1,tail1;
int head2,tail2;
ll maxn[MAXN][MAXN],minn[MAXN][MAXN];
ll ans=1ll<<62;
int main()
{
scanf("%d%d%d",&a,&b,&n);
for(int i=1;i<=a;i++)
{
for(int j=1;j<=b;j++) scanf("%lld",&x[i][j]);
}
for(int j=1;j<=a;j++)
{
head1=0,tail1=1;
memset(q1,0,sizeof(q1)),memset(loc1,0,sizeof(loc1));
for(int i=1;i<=b;i++)
{
while(head1>=tail1&&q1[head1]<=x[j][i]) head1--;
q1[++head1]=x[j][i],loc1[head1]=i;
if(loc1[tail1]<i-n+1) tail1++;
if(i>=n) maxn[j][i-n+1]=q1[tail1];
}
}
for(int j=1;j<=a;j++)
{
head1=0,tail1=1;
memset(q1,0,sizeof(q1)),memset(loc1,0,sizeof(loc1));
for(int i=1;i<=b;i++)
{
while(head1>=tail1&&q1[head1]>=x[j][i]) head1--;
q1[++head1]=x[j][i],loc1[head1]=i;
if(loc1[tail1]<i-n+1) tail1++;
if(i>=n) minn[j][i-n+1]=q1[tail1];
}
}
for(int j=1;j+n-1<=b;j++)
{
head1=0,tail1=1;
memset(q1,0,sizeof(q1)),memset(loc1,0,sizeof(loc1));
head2=0,tail2=1;
memset(q2,0,sizeof(q2)),memset(loc2,0,sizeof(loc2));
for(int i=1;i<=a;i++)
{
while(head1>=tail1&&minn[i][j]<=q1[head1]) head1--;
q1[++head1]=minn[i][j],loc1[head1]=i;
if(loc1[tail1]<i-n+1) tail1++;
while(head2>=tail2&&maxn[i][j]>=q2[head2]) head2--;
q2[++head2]=maxn[i][j],loc2[head2]=i;
if(loc2[tail2]<i-n+1) tail2++;
if(i>=n) ans=min(ans,q2[tail2]-q1[tail1]);
}
}
printf("%lld\n",ans);
return 0;
}
单调队列优化 dp,直接维护一个长度为 \(2t+1\) 的窗口然后特判窗口小的情况即可。
时间复杂度是 \(\mathcal O(nm)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
int n,m,t,k;
int a[4005][4005];
int l,r,v;
int dp[4005][4005];
int q[4005],loc[4005],head=0,tail=1;
int ans=0;
int main()
{
read(n),read(m),read(k),read(t);
for(int i=1;i<=k;i++)
{
read(l),read(r),read(v);
a[l][r]=v;
}
for(int i=1;i<=m;i++) dp[1][i]=a[1][i];
if(m<=t+1)
{
int ans=0;
for(int i=1;i<=n;i++)
{
int now=0;
for(int j=1;j<=m;j++) now=max(now,a[i][j]);
ans+=now;
}
printf("%d\n",ans);
return 0;
}
for(int i=2;i<=n;i++)
{
memset(q,0,sizeof(q)),memset(loc,0,sizeof(loc));
head=0,tail=1;
for(int j=1;j<=m;j++)
{
while(head>=tail&&q[head]<=dp[i-1][j]) head--;
q[++head]=dp[i-1][j],loc[head]=j;
if(loc[tail]<j-2*t) tail++;
if(j>=t+1) dp[i][j-t]=q[tail]+a[i][j-t];
}
for(int j=1;j<=t;j++)
{
if(loc[tail]<m-t-t+j) tail++;
dp[i][m-t+j]=q[tail]+a[i][m-t+j];
}
}
for(int i=1;i<=m;i++) ans=max(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}
我们考虑把一个点的所有倍数和他连起来这样的复杂度仅仅是 \(\mathcal O(n\ln n)\) 的。
然后把所有联系的连起来,只需要看一下 \(w,e\) 是否在一个强联通分量里就好了。
考虑缩点。
最后有贡献的只能是入读为零的点,对缩完后的点取最小价值即可。
我很菜写了一个 \(\mathcal O(pn+m)\) 的算法。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define MAXN 8005
#define read(x) scanf("%d",&x)
int n,p,m;
int id=0,x,y;
int low[MAXN],num[MAXN];
int a[MAXN];
struct node
{
int to,nxt;
}e[MAXN];
int head[MAXN],cnt=0;
int be[MAXN],si[MAXN],c=0;
int st[MAXN],top=0,vis[MAXN];
int val[MAXN];
int in[MAXN];
int l[MAXN],r[MAXN];
int ans=0;
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfss(int cur)
{
vis[cur]=1;
for(int i=head[cur];i;i=e[i].nxt) if(!vis[e[i].to]) dfss(e[i].to);
return;
}
void dfs(int cur)
{
low[cur]=num[cur]=++id;
vis[cur]=1,st[++top]=cur;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j]) dfs(j);
if(!be[j]) low[cur]=min(low[cur],low[j]);
}
if(low[cur]==num[cur])
{
c++;
while(1)
{
int k=st[top--];
be[k]=c,si[c]++,val[c]=min(val[c],a[k]);
if(k==cur) break;
}
}
}
int main()
{
read(n),read(p);
for(int i=1;i<=n;i++) a[i]=val[i]=0x7fffffff;
for(int i=1;i<=p;i++) read(x),read(y),a[x]=y;
read(m);
for(int i=1;i<=m;i++) read(l[i]),read(r[i]),add(l[i],r[i]);
for(int i=1;i<=n;i++) if(!vis[i]&&a[i]<0x7fffffff) dfss(i);
for(int i=1;i<=n;i++) if(!vis[i]) return printf("NO\n%d\n",i),0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
memset(e,0,sizeof(e)),memset(head,0,sizeof(head)),cnt=0;
for(int i=1;i<=m;i++) if(be[l[i]]^be[r[i]]) add(be[l[i]],be[r[i]]),in[be[r[i]]]++;
for(int i=1;i<=c;i++) if(!in[i]) ans+=val[i];
printf("YES\n%d\n",ans);
return 0;
}
因为 \(dis_j<dis_i+w_{i to j}\) 实现所有最短路的充要条件,所有这样构图然后最短路即可。
啥时候无解?存在负环。
提醒:队列的空间要开大,大概要有 \(n^2\) 的最坏空间,所以建议用 STL 的队列。
时间复杂度 \(\mathcal O(kn)\) 。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 5005
#define read(x) scanf("%d",&x)
int n,m;
int x,y,z;
struct node
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int dis[MAXN],vis[MAXN];
int cou[MAXN]={0},que[MAXN*MAXN],he=1,ta=1;
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].w=w;
head[u]=cnt;
}
void SPFA()
{
que[1]=0,vis[0]=1,dis[0]=0;
while(he>=ta)
{
int u=que[ta++];
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+e[i].w)
{
dis[j]=dis[u]+e[i].w,cou[j]=cou[u]+1;
if(!vis[j]) que[++he]=j,vis[j]=1;
if(cou[j]>=n+2){puts("NO");return;}
}
}
}
for(int i=1;i<=n;i++) printf("%d ",dis[i]);
puts("");
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++) read(x),read(y),read(z),add(y,x,z);
for(int i=1;i<=n;i++) add(0,i,0);
for(int i=1;i<=n;i++) dis[i]=1e9;
SPFA();
return 0;
}
差分约束练手题。
A不了。。。。。
最短路是错的,需要最长路,然后对于符号相反的约束移一下项就可以了。
把非等于化成可以等于的,可以加减 \(1\)。
冷静分析发现只有出现环才会错。
所以把没在环上的先踢出去,然后枚举每个没走过的边,搜的时候尽量搜没走过的边,然后判断一下次环是否合法即可。
有一个不合法的就不行。
水一波月赛签到题/cy
最多子串一定是一个字母。
一个男生同时能和多个女生搞(引发社会恐慌
并查集一下找能玩男生最少的集合能玩几个,加上 \(k\) 然后和 \(n\) 取 \(\min\) 即可。
\(70.\) P2323 [HNOI2006]公路修建问题
这玩意好像叫最小瓶颈树来着。(好像不是,别听我的啊
先取大边,如果一样优先选小边最大的。
然后取小边,可以证明大边取的越少越好,然后直接最小生成树,复杂度 \(\mathcal O(n\log m)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 20005
int n,m,k;
int maxn=0;
struct node
{
int l,r,v,w,u,id;
node(){u=0;}
}a[MAXN];
int rt[MAXN],f[MAXN];
int len=0;
bool cmp1(node n,node m)
{
if(n.v==m.v) return n.w>m.w;
else return n.v<m.v;
}
bool cmp2(node n,node m)
{
if(n.u==m.u) return n.w<m.w;
else return n.u<m.u;
}
int getf(int u){return f[u]=(f[u]==u)?u:getf(f[u]);}
int merge(int u,int v)
{
int t1=getf(u),t2=getf(v);
if(t1==t2) return 0;
f[t2]=t1;
return 1;
}
int main()
{
read(n),read(k),read(m);
m-=1;
for(int i=1;i<=m;i++) read(a[i].l),read(a[i].r),read(a[i].v),read(a[i].w),a[i].id=i;
for(int i=1;i<=n;i++) f[i]=i;
sort(a+1,a+m+1,cmp1);
//for(int i=1;i<=m;i++) cout<<a[i].id<<endl;
for(int i=1;i<=m;i++)
{
if(merge(a[i].l,a[i].r))
{
maxn=max(maxn,a[i].v);
len++;
a[i].u=1;
rt[a[i].id]=1;
if(len==k) break;
}
}
sort(a+1,a+m+1,cmp2);
for(int i=1;i<=m;i++)
{
if(merge(a[i].l,a[i].r))
{
maxn=max(maxn,a[i].w);
len++;
rt[a[i].id]=2;
if(len==n-1) break;
}
}
printf("%d\n",maxn);
for(int i=1;i<=m;i++)
{
if(!rt[i]) continue;
printf("%d %d\n",i,rt[i]);
}
return 0;
}
其实一个空位可以放多个数。
设到第 \(i\) 个位置放了 \(j\) 个 \(0\),\(k\) 个 \(1\),那么:
由于特殊数据范围,复杂度可以做到 \(\mathcal O(nab)\)。
坑,要开 ull
,可以学习一下占位符...
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 55
#define ll unsigned long long
ll dp[MAXN][MAXN][MAXN];
int n,a,b;
ll sum=0;
int main()
{
read(n),read(a),read(b);
dp[0][0][0]=1ull;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=a;j++)
{
for(int k=0;k<=b;k++)
{
for(int g=j;g<=a;g++)
{
for(int h=k;h<=b;h++)
{
dp[i][g][h]+=dp[i-1][j][k];
}
}
}
}
}
for(int i=0;i<=a;i++) for(int j=0;j<=b;j++) sum=sum+dp[n][i][j];
printf("%llu\n",sum);
return 0;
}
\(72\) P3545 [POI2012]HUR-Warehouse Store
考虑贪心。
发现需求小的顾客更合算,那就按需求从小到大排序,然后判断一下是否能满足。
发现可以用前缀和表示一下库存,然后显然是一个资瓷区间减与最小值的线段树。
时间复杂度 \(\mathcal O(n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;
#define MAXN 250005
#define read(x) scanf("%lld",&x)
#define ll long long
int n,bo[MAXN],cnt=0;;
ll sum[MAXN],x;
struct P
{
ll val;
int id;
}b[MAXN];
struct node
{
ll rec,lzy;
int l,r;
node(){rec=lzy=0;l=r=0;}
}a[MAXN<<2];
bool cmp(P n,P m){return n.val<m.val;}
inline void update(int k){a[k].rec=min(a[k<<1].rec,a[k<<1|1].rec);}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].rec=sum[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
}
void lazydown(int k)
{
a[k<<1].lzy+=a[k].lzy,a[k<<1|1].lzy+=a[k].lzy;
a[k<<1].rec-=a[k].lzy,a[k<<1|1].rec-=a[k].lzy;
a[k].lzy=0;
}
void modify(int k,int l,int r,ll x)
{
if(a[k].l==l&&a[k].r==r){a[k].lzy+=x,a[k].rec-=x;return;}
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
update(k);
}
ll query(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k].rec;
lazydown(k);
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return min(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) read(x),sum[i]=sum[i-1]+x;
for(int i=1;i<=n;i++) read(b[i].val),b[i].id=i;
sort(b+1,b+n+1,cmp),build(1,1,n);
for(int i=1;i<=n;i++)
{
ll now=query(1,b[i].id,n);
if(b[i].val<=now) bo[b[i].id]=++cnt,modify(1,b[i].id,n,b[i].val);
}
printf("%d\n",cnt);
for(int i=1;i<=n;i++) if(bo[i]) printf("%d ",i);
return puts(""),0;
}
其实这玩意无后效性还是挺明显的。
我们记 \(dp_i\) 为右边比左边高 \(i\) 时右边最高为多少。
有负数下标,所以平移一下。
不能随时更新,所以把可行状态先记录一下,然后赋值,时间复杂度为 \(\mathcal O(n\sum h)\)。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 55
#define B 500002
int n;
int a[MAXN],dp[1000005];
int d[1000005];
int main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=B*2;i++) dp[i]=-1;
dp[B-a[1]]=dp[B]=0,dp[B+a[1]]=a[1];
for(int i=2;i<=n;i++)
{
for(int j=0;j<=B*2;j++) d[j]=dp[j];
for(int j=a[i];j<=B*2-a[i];j++)
{
if(dp[j]!=-1)
{
d[j-a[i]]=max(d[j-a[i]],dp[j]);
d[j+a[i]]=max(d[j+a[i]],dp[j]+a[i]);
}
}
for(int j=1;j<=2*B;j++) dp[j]=max(dp[j],d[j]);
}
if(dp[B]>0) printf("%d\n",dp[B]);
else printf("-1\n");
return 0;
}
黄题都不会做了/kk
冷静模拟一下,可以发现用一种分治思想,对于 \(a_i\)
分开考虑下标比 \(i\) 小的,和其他的,发现系数很有规律,而且好递推。
具体式子我就不写了,逃~
可以做到 \(\mathcal O(n)\) 。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define ll long long
#define MAXN 500005
#define MOD 1000000007
ll a[MAXN],b[MAXN],ans=0;
int n;
ll now=0;
ll ba=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
for(int i=1;i<=n;i++) now=(now+(n-i+1)*b[i]%MOD)%MOD;
for(int i=1;i<=n;i++)
{
ans=(ans+((n-i+1)*ba%MOD+i*(now)%MOD)*a[i]%MOD%MOD)%MOD;
ba=(ba+i*b[i]%MOD)%MOD;
now=(now-(n-i+1)*b[i]%MOD+MOD)%MOD;
}
printf("%lld\n",ans%MOD);
return 0;
}
问题很难考虑
毕竟是 ZJOI 的题吗(可这还是蓝题啊喂
反向建边,然后向下拓扑排序,发现如果一个点连很多边,那么只有灭绝他所有父亲的 LCA 才可,所以连到 LCA 上,当然这保证了上边是一棵树。
最后答案就是子树大小减去自己。
发现要输出的数很少,所以枚举每一个数,逆推过程找一下他的本源。
\(77\) CF873C Strange Game On Matrix
随便做就好了(bushi
前缀和维护一下 1
的个数,然后算每个区间
考虑大区间珂以覆盖前面的所有比他小的区间,所以每次维护尽可能最大的区间的空隙即可。
能做到 \(\mathcal O(m+n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;
#define MAXN 200005
#define read(x) scanf("%d",&x)
int n,m;
int b[MAXN],a[MAXN];
int num[MAXN];
struct node
{
int t,v,id;
}op[MAXN],r[MAXN];
int u[MAXN]={0},maxn=1,rn;
int l,ri;
int lst;
bool cmp(node n,node m){if(n.v==m.v) return n.id>m.id;else return n.v>m.v;}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(b[i]);
for(int i=1;i<=m;i++) read(op[i].t),read(op[i].v),op[i].id=i,r[i]=op[i];
sort(op+1,op+m+1,cmp),rn=n;
for(int i=1;i<=m;i++) r[op[i].id].id=i;
for(int i=1;i<=m;i++)
{
u[r[i].id]=1;
if(r[i].id==maxn)
{
if(maxn==1)
{
for(int j=r[i].v+1;j<=rn;j++) num[j]=b[j];
for(int j=1;j<=r[i].v;j++) a[j]=b[j];
sort(a+1,a+r[i].v+1);
l=1,ri=r[i].v;
rn=r[i].v;
}
else
{
if(lst==2)
{
for(int j=1;j<=rn-r[i].v;j++) num[rn-j+1]=a[l+j-1];
l=l+rn-r[i].v;
}
else
{
for(int j=1;j<=rn-r[i].v;j++) num[rn-j+1]=a[ri-j+1];
ri=ri-rn+r[i].v;
}
lst=r[i].t;
rn=r[i].v;
}
lst=r[i].t;
while(u[maxn]&&maxn<=n) maxn++;
}
}
if(lst==1) for(int i=l;i<=ri;i++) num[i-l+1]=a[i];
else for(int i=ri;i>=l;i--)num[ri-i+1]=a[i];
for(int i=1;i<=n;i++) printf("%d ",num[i]);
return puts(""),0;
}
挺不错的一道 dp。
考虑依题意设出 \(dp_{i,j,k}\)。
然后有:
注意一下边界。
你发现要五重循环,然后 \(k\) 的取值范围保证时间复杂度是 \(\mathcal O(nm(n+m)k^2+t)\) 并不会超时。
#include"iostream"
#include"cmath"
#include"cstring"
#include"algorithm"
using namespace std;
#define read(x) scanf("%d",&x)
int dp[35][35][55];
int t,n,m,k;
int main()
{
read(t);
dp[1][1][1]=0;
dp[1][1][0]=0;
for(int i=1;i<=31;i++)
{
for(int j=1;j<=31;j++)
{
if(i==1&&j==1) continue;
int op=min(i*j,52);
for(int k=1;k<op;k++)
{
dp[i][j][k]=100000000;
for(int s=1;s<=(i/2)+1&&i-s>=0;s++)
{
for(int q=0;q<=s*j&&q<=k;q++)
{
if((k-q)>(i-s)*j) continue;
dp[i][j][k]=min(dp[i][j][k],dp[i-s][j][k-q]+dp[s][j][q]+j*j);
}
}
for(int s=1;s<=(j/2)+1&&j-s>=0;s++)
{
for(int q=0;q<=i*s&&q<=k;q++)
{
if(k-q>i*(j-s)) continue;
dp[i][j][k]=min(dp[i][j][k],dp[i][j-s][k-q]+dp[i][s][q]+i*i);
}
}
}
}
}
while(t--)
{
read(n),read(m),read(k);
printf("%d\n",dp[n][m][k]);
}
return 0;
}
对于树的部分分,只要看懂题就随便做了是不是?
60 分,ccf 感人放送。
对于基环树的部分,我们由题意模拟一下发现有一条在环上的边是不用走的,所以暴力枚举被删掉的边,然后一样做就好了。
菜菜菜
考虑贪心。
每一个子树都期望匹配到最多的合法赛道,所以在二分答案的同时,每次向下 DFS 在子树内匹配,再将无法匹配到的最大的边(想想为什么)传上去加到上面的贡献,最后判断是否有这些赛道可修建就好了,时间复杂度是 \(\mathcal O(n\log^2n)\)。
找规律好题
我们观察下表发现第 \(i\) 个 \(k+2\) 边形数为 \(k\dfrac{i(i-1)}{2}+i\) 。
我们首先考虑把 \(i\) 解出来:
化简可知:
想法 \(1:\)
首先保证根号下是整数,然后在保证相除仍为整数。
具体做法?
枚举 \(k\),然而易知 \(k_{max}\) 是 \(\mathcal O(N)\) 级别的,会炸掉。
想法 \(2:\)
直接枚举 \(i\) 的取值,
由于三角形数通项公式很容易得到是:
作为最小的多边形数,\(i\) 的取值就被限制在 \(\mathcal O(\sqrt{N})\) 级别内,不会炸掉。
更进一步来说,我们与其用 \(k\) 来表示 \(i\) ,不如用 \(i\) 表示 \(k\)。
易得:
当然 \(i=1\) 的情况(即 \(N=1\) )我们可以特判一下。
为了容易比较我们可以计算该函数的单调性,对其求导可知:
易知该函数单调递减,所以从大(大概是从 \(\mathcal O(\sqrt{2n})\) 左右开始枚举,注意始终保证 \(N-i>0\))到小(不能取 \(1\))枚举 \(i\)找到两个合法的就行了。
总的时间复杂度是 \(\mathcal O(T\sqrt{N})\)。
\(\mathcal {Code:}\)(可能略微难看,见谅)
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
int n,x;
int k1,k2;
int main()
{
read(n);
while(n--)
{
k1=0,k2=0;
read(x);
if(x==1){printf("3 4\n");continue;}
for(int i=sqrt(2*x)+1;i>=2;i--)
{
if(x<=i) continue;
if(2*(x-i)%(i*(i-1))==0)
{
if(!k1) k1=2*(x-i)/(i*(i-1))+2;
else
{
k2=2*(x-i)/(i*(i-1))+2;
break;
}
}
}
if(!k1) printf("Poor%d\n",x);
else if(!k2) printf("%d\n",k1);
else printf("%d %d\n",k1,k2);
}
return 0;
}
由于是单点查询所以线段树很好搞,具体的话就是差分。
时间复杂度是 \(\mathcal O(m\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
#define ll long long
int n,m;
struct node
{
int l,r;
ll sum,lzy;
node(){l=r=0;sum=lzy=0;}
}a[MAXN<<2];
int opt,l,r,K,D;
int rt[MAXN],b[MAXN];
inline void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum;}
void build(int l,int r,int k)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].sum=b[l];return;}
int mid=(l+r)>>1;
build(l,mid,k<<1),build(mid+1,r,k<<1|1);
update(k);
return;
}
inline void lazydown(int k)
{
if(a[k].l==a[k].r){a[k].lzy=0;return;}
a[k<<1].sum=a[k<<1].sum+a[k].lzy*(ll)(a[k<<1].r-a[k<<1].l+1);
a[k<<1|1].sum=a[k<<1|1].sum+a[k].lzy*(ll)(a[k<<1|1].r-a[k<<1|1].l+1);
a[k<<1].lzy+=a[k].lzy,a[k<<1|1].lzy+=a[k].lzy;
a[k].lzy=0;
return;
}
void modify(int k,int l,int r,ll x)
{
if(a[k].l==l&&a[k].r==r)
{
a[k].sum=a[k].sum+(ll)(a[k].r-a[k].l+1)*x;
a[k].lzy+=x;
return;
}
if(a[k].lzy) lazydown(k);
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
update(k);
}
ll query(int k,int l,int r)
{
if(a[k].lzy) lazydown(k);
if(a[k].l==l&&a[k].r==r) return a[k].sum;
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(rt[i]);
b[1]=rt[1];
for(int i=2;i<=n;i++) b[i]=rt[i]-rt[i-1];
build(1,n,1);
while(m--)
{
read(opt);
if(opt==1)
{
read(l),read(r),read(K),read(D);
modify(1,l,l,(ll)K);
if(l!=r) modify(1,l+1,r,(ll)D);
if(r<n) modify(1,r+1,r+1,(ll)(-K-D*(r-l)));
}
else
{
read(r);
printf("%lld\n",query(1,1,r));
}
}
return 0;
}
做题发现自己不会 \(\mathcal O(n\log n)\) \(LIS\) 了,怎么办?看题解!
线段树!
我们考虑从后向前枚举每一个数,如果设 \(dp_i\) 为以 \(i\) 开头的最长下降子序列的长度,那么只需要求得在他后面比他大的数的 \(dp\) 值的最大值。
所以我们考虑将权值离散化,将没有扫过(显然这些数在现在这个数前面)的数的 \(dp\) 值设得很小,然后线段树上二分求一下最大值就好了了。
时间复杂度就是常数比较大的 \(\mathcal O(n\log n)\)。
对于询问,考虑贪心即可。
由于数据是随机的,这个 \(\mathcal O(nm)\) 的询问复杂度也不会跑得很满,所以就很轻松的过掉了。
参考代码:
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 10005
struct num
{
int val,id;
}b[MAXN];
int n,m;
struct node
{
int l,r,rec;
}a[MAXN<<2];
int re[MAXN],dp[MAXN];
int l,me[MAXN];
bool cmp(num n,num m){if(n.val==m.val) return n.id>m.id;else return n.val<m.val;}
void hash()
{
sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;i++) re[b[i].id]=i,dp[i]=-1;
}
void update(int k){a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].rec=dp[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
return;
}
void modify(int k,int x,int y)
{
if(a[k].l==a[k].r){a[k].rec=y;return;}
if(a[k<<1].r>=x) modify(k<<1,x,y);
else modify(k<<1|1,x,y);
update(k);
}
int query(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k].rec;
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
int main()
{
read(n);
for(int i=1;i<=n;i++) read(b[i].val),me[i]=b[i].val,b[i].id=i;
hash(),build(1,1,n);
for(int i=n;i>=1;i--)
{
int now=query(1,re[i],n);
if(now==-1) dp[i]=1;
else dp[i]=now+1;
modify(1,re[i],dp[i]);
}
read(m);
while(m--)
{
read(l);
int now=l,lst=-0x7fffffff;
for(int i=1;i<=n;i++)
{
if(dp[i]>=now&&me[i]>lst)
{
now--,lst=me[i];
printf("%d ",me[i]);
if(now==0) break;
}
}
if(now>0) printf("Impossible");
puts("");
}
return 0;
}
这大概是正难则反的典例了吧。
考虑反向建图,这时候只需要让编号小的数靠后出现,所以用大根堆来维护待排序的点,将逆拓扑序列翻转即求得答案。
对于出现环(具体表示为无法进堆)的情况,输出 "Impossible!" 即可。
时间复杂度是 \(\mathcal O(Dn\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
#include"queue"
using namespace std;
#define MAXN 100005
#define read(x) scanf("%d",&x)
#define mem(s) memset(s,0,sizeof(s))
struct node
{
int to,nxt;
}e[MAXN];
int head[MAXN],cnt=0;
int n,m,t;
int x,y;
int ind[MAXN];
priority_queue<int> q;
int rt[MAXN],c=0;
int vis[MAXN];
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
int main()
{
read(t);
while(t--)
{
mem(e),mem(head),mem(ind),mem(rt),mem(vis);
cnt=0,c=0;
read(n),read(m);
for(int i=1;i<=m;i++) read(x),read(y),add(y,x),ind[x]++;
for(int i=1;i<=n;i++) if(!ind[i]) q.push(i);
if(q.empty()){printf("Impossible!\n");continue;}
while(!q.empty())
{
int u=q.top();
q.pop();
rt[++c]=u;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(vis[j]) continue;
ind[j]--;
if(!ind[j]) vis[j]=1,q.push(j);
}
}
if(c!=n){printf("Impossible!\n");continue;}
for(int i=n;i>=1;i--) printf("%d ",rt[i]);
puts("");
}
return 0;
}
超超超级恶心的一道字符串题目,虽然只考察字符串基本操作。
题目很是简单,发现是背包这个东西有序啊!
贪心很容易,所以按拼接后大小排序即可。
不精细操作可以做到 \(\mathcal O(nD^2)\)。(竟然稳过?)
注意小数处理较为恶心。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"algorithm"
using namespace std;
#define read(x) scanf("%d",&x)
string dp[205],maxn;
int vis[205];
int D,n;
char c[35],e[205];
string s[10005];
int len[10005];
int flag=0;
int tran(char c)
{
int x;
if(c=='O'||c=='D') x=0;
else if(c=='G') x=9;
else if(c=='B') x=8;
else if(c=='L') x=7;
else if(c=='q') x=6;
else if(c=='S') x=5;
else if(c=='h') x=4;
else if(c=='E') x=3;
else if(c=='Z') x=2;
else if(c=='I') x=1;
return x;
}
bool cmp(string s,string t)
{
int l=s.size();
for(int i=l-1;i>=0;i--)
{
int op=tran(s[i]),rt=tran(t[i]);
if(op>rt) return 1;
else if(op<rt) return 0;
}
return 0;
}
bool cmppp(string a,string b)//p 太多辣!
{
int lena=a.size(),lenb=b.size();
for(int i=lena-1;i>=0;i--)
{
int op=tran(a[i]),rt=tran(b[i-lena+lenb]);
if(op>rt) return 1;
else if(op<rt) return 0;
}
return 0;
}
bool cmpp(string a,string b)
{
if(cmp(a+b,b+a)) return 1;
else return 0;
}
int main()
{
read(D),read(n);
for(int i=1;i<=n;i++) scanf("%s",c),s[i]=c;
sort(s+1,s+n+1,cmpp);
for(int i=1;i<=n;i++) len[i]=s[i].size();
for(int i=1;i<=D;i++)
{
for(int j=0;j<i;j++) e[j]='O';
dp[i]=e;
}
vis[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=D-len[i];j>=0;j--)
{
if(!vis[j]) continue;
vis[j+len[i]]=1;
string o=dp[j],t=s[i],ans;
if(o=="") ans=s[i];
else
{
string ss=o+t,tt=t+o;
if(cmp(ss,tt)) ans=ss;
else ans=tt;
}
if(cmp(ans,dp[j+len[i]])) dp[j+len[i]]=ans;
}
}
int k;
for(k=D;k>=0;k--) if(vis[k]) break;
if(dp[k][k-1]=='O'||dp[k][k-1]=='D')
{
if(k==1) return puts("0"),0;
string ans=dp[k];
int lenn=k;
for(int i=k;i>=1;i--)
{
if(cmppp(dp[i],ans)) ans=dp[i],lenn=i;
}
printf("0.");
for(int i=lenn-2;i>=0;i--) printf("%d",tran(ans[i]));
puts("");
}
else
{
for(int i=k-1;i>=0;i--) printf("%d",tran(dp[k][i]));
puts("");
}
return 0;
}
考虑删去最短路上的每一条边,然后跑最短路,取一遍 \(\max\) 就好了。
时间复杂度是 \(\mathcal O(n^2\log n)\)。
\(88\) P2915 [USACO08NOV]Mixed Up Cows G
数据范围真就状压既视感呗.jpg
我们考虑按顺序(从前向后,不留空位)给奶牛排位置,然后影响转移的就只有最后一只奶牛了。
于是设 \(dp_{s,i}\) 为选择状态为 \(s\) 且最后一只奶牛编号为 \(i\) 时的方案数。
所以有:
时间复杂度是 \(\mathcal O(2^nn^2)\)
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 17
#define ll long long
ll dp[1<<MAXN][MAXN];
int n,val[MAXN],k;
int vis[MAXN];
ll ans=0;
int main()
{
read(n),read(k);
for(int i=1;i<=n;i++) read(val[i]);
for(int i=1;i<=n;i++) dp[1<<(i-1)][i]=1ll;
for(int s=1;s<(1<<n);s++)
{
memset(vis,0,sizeof(vis));
for(int j=0;j<n;j++) if((1<<j)&s) vis[j+1]=1;
for(int j=1;j<=n;j++)
{
if(!vis[j]) continue;
if(!dp[s][j]) continue;
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
if(abs(val[i]-val[j])<=k) continue;
dp[s^(1<<(i-1))][i]+=dp[s][j];
}
}
}
for(int i=1;i<=n;i++) ans+=dp[(1<<n)-1][i];
printf("%lld\n",ans);
return 0;
}
由于集训模拟赛两道大细节题(字符串大膜你+复杂高精)把我送退役,故来刷 NOIP 有关模拟的题。
不得不说比集训的那俩良心多了。
直接模拟即可,我的复杂度是 \(\mathcal O(TL)\)?
论时间复杂度的时间复杂度/cy/cy
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define mem(s) memset(s,0,sizeof(s))
int t,n,w;
int st[105],top=0;;
char s[10];
int er=0;
int vis[30];
int bo[105];
int main()
{
scanf("%d",&t);
while(t--)
{
mem(st),mem(vis),mem(bo);
er=0,top=0;
scanf("%d",&n);
scanf("%s",s);
if(s[2]=='1') w=0;
else
{
if(s[5]>='0'&&s[5]<='9') w=(s[4]-'0')*10+s[5]-'0';
else w=s[4]-'0';
}
int now=0,ty=0,flg=0;
for(int k=1;k<=n;k++)
{
scanf("%s",s);
if(s[0]=='F')
{
top++;
scanf("%s",s);
if(vis[s[0]-'a'+1]==1) er=1;
st[top]=s[0]-'a'+1;
vis[st[top]]=1;
scanf("%s",s);
if(s[0]>='0'&&s[0]<='9')
{
int fir,sec;
if(strlen(s)==2) fir=(s[0]-'0')*10+s[1]-'0';
else fir=s[0]-'0';
scanf("%s",s);
if(s[0]=='n')
{
if(!flg) ty++,bo[top]=1;
}
else
{
if(strlen(s)==2) sec=(s[0]-'0')*10+s[1]-'0';
else sec=s[0]-'0';
if(sec<fir) flg=1,bo[top]=2;
}
}
else
{
scanf("%s",s);
if(s[0]!='n') flg=1,bo[top]=2;
}
now=max(now,ty);
}
else if(s[0]=='E')
{
if(top)
{
vis[st[top]]=0;
if(bo[top]==1) ty--,bo[top]=0;
else if(bo[top]==2) flg=0,bo[top]=0;
top--;
}
else er=1;
}
}
if(top) er=1;
if(er) puts("ERR");
else if(now!=w) puts("No");
else puts("Yes");
}
return 0;
}
同余最短路。
考虑在 \(\bmod x\) 意义下,到达 \(j\) 至少要到的高度。
可以用最短路实现优化,最后记录答案即可。
注意起点是 \(1\) 而不是 \(0\)。
时间复杂度是 \(\mathcal O(n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"queue"
#include"vector"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
#define ll long long
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
ll dis[MAXN];
int vis[MAXN];
int x,y,z;
ll h,ans=0;
struct node
{
int to,nxt;
ll w;
}e[MAXN<<1];
int head[MAXN],cnt=0;
void add(int u,int v,ll w){e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;}
void dij()
{
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
if(dis[j]>dis[u]+e[i].w) dis[j]=dis[u]+e[i].w,q.push(make_pair(dis[j],j));
}
}
return;
}
int main()
{
scanf("%lld",&h);
read(x),read(y),read(z);
if(x==1||y==1||z==1) return printf("%lld\n",h),0;
for(int i=0;i<=x-1;i++) add(i,(i+z)%x,(ll)z),add(i,(i+y)%x,(ll)y);
dis[1]=1ll;
q.push(make_pair(1ll,1));
for(int i=0;i<=x-1;i++) if(i!=1) dis[i]=(ll)((1ull<<63)-1);
dij();
for(int i=0;i<=x-1;i++) if(dis[i]<=h) ans+=(h-dis[i])/(ll)x+1;
printf("%lld\n",ans);
return 0;
}
特别好的一道 dp,我们考虑把背包优化一下。
当我们枚举不选哪个物品时,总方案数显然不能从 \(j-w_i\) 这个位置转移来,减去即可(原谅我 \(w,v\) 用混了吧 qwq)
最后注意减去的这部分转移也要保证不能选这个数。
时间复杂度是 \(\mathcal O(n^2)\)。
(ps:这题我其实想到了 FFT 的无脑做法,有科技就是好啊然而我忘了怎么写了
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 2005
int dp[MAXN][2];
int n,m,w[MAXN];
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(w[i]);
dp[0][0]=dp[0][1]=1;
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)
{
dp[j][0]=(dp[j][0]+dp[j-w[i]][0])%10;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j>=w[i]) dp[j][1]=(dp[j][0]-dp[j-w[i]][1]+10)%10;//注意减去的也要保证不拿这个的条件
else dp[j][1]=dp[j][0];
printf("%d",dp[j][1]);
}
puts("");
}
return 0;
}
\(92.\) P3431 [POI2005]AUT-The Bus
啊,一眼离散化。
这玩意是 dp 啊,区间最大值?
考虑线段树(原谅我不会树状数组吧......)
然后对于每一行覆盖过去就完事了,时间复杂度是是 \(\mathcal O(k\log k)\)。
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
int n,m,k;
int xx[MAXN],yy[MAXN];
struct node
{
int val,id;
}aa[MAXN];
struct P
{
int x,y,p;
}op[MAXN];
int to[MAXN];
struct Tree
{
int l,r,rec;
}a[MAXN<<2];
int cnt=0,lst=0,ans=0;
int logx(int x){return floor(log(x)/log(2));}
bool cmp(node n,node m){return n.val<m.val;}
bool cmpp(P n,P m){if(n.y==m.y) return n.x<m.x;else return n.y<m.y;}
void hash()
{
sort(aa+1,aa+k+1,cmp),cnt=0,lst=0;;
for(int i=1;i<=k;i++)
{
if(aa[i].val!=lst) cnt++,lst=aa[i].val;
to[aa[i].id]=cnt;
}
return;
}
void update(int k){a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].rec=0;return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
}
void turn(int k,int x,int y)
{
if(a[k].l==a[k].r){a[k].rec=y;return;}
(x<=a[k<<1].r)?turn(k<<1,x,y):turn(k<<1|1,x,y);
update(k);
}
int query(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k].rec;
int mid=a[k<<1].r;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
int main()
{
read(n),read(m),read(k);
for(int i=1;i<=k;i++) read(xx[i]),read(yy[i]),read(op[i].p);
for(int i=1;i<=k;i++) aa[i].val=xx[i],aa[i].id=i;
hash();
int h=cnt;
for(int i=1;i<=k;i++) op[i].x=to[i];
for(int i=1;i<=k;i++) aa[i].val=yy[i],aa[i].id=i;
hash();
for(int i=1;i<=k;i++) op[i].y=to[i];
sort(op+1,op+k+1,cmpp);
build(1,1,h);
for(int i=1;i<=k;i++)
{
int now=query(1,1,op[i].x);
ans=max(ans,now+op[i].p);
turn(1,op[i].x,now+op[i].p);
}
printf("%d\n",ans);
return 0;
}
可以反悔的贪心。
考虑能选就选。
如果把从前选过的一天的方案与今天互换更优的话就换掉,可以用堆来维护。
时间复杂度是 \(\mathcal O(n\log n)\)。
\(94.\) P2949 [USACO09OPEN]Work Scheduling G
可可反悔贪心经典题例。
能选就选(这是建立在按结束日期排序的基础上的),如果不能选把前边比他劣的替换掉即可。
同样是用堆来优化,可以做到 \(\mathcal O(n\log n)\)。
考虑二进制。
贪心的把最小的数二进制为 \(0\) 的位置改成 \(1\),然后和最大值比大小即可。
这时候我们要从最小的位置开始贪心。
时间复杂度是 \(\mathcal O(\log n)\)。
\(96.\) P3052 [USACO12MAR]Cows in a Skyscraper G
正解是状压(这个数据一看就是啊。。。
但是我是搜贪心标签做的,所以就贪心的每次选择使得剩下的体积最小即可。
所以 dfs 就好了,时间复杂度是 \(\mathcal O(2^nn^2)\)???
算了我也不知道
#include"algorithm"
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 20
int n,W,c[MAXN];
int vis[MAXN];
int cnt=0,minx;
int ma[MAXN],sta[MAXN];
int r;
bool cmp(int n,int m){return n>m;}
void dfs(int l,int x)
{
if(x<c[r]||l==r)
{
if(x<minx)
{
minx=x;
for(int i=1;i<=n;i++) sta[i]=ma[i];
}
else if(x==minx)
{
int c1=0,c2=0;
for(int i=1;i<=n;i++)
{
if(sta[i]) c1++;
if(ma[i]) c2++;
}
if(c1<c2)
{
for(int i=1;i<=n;i++) sta[i]=ma[i];
}
}
return;
}
for(int i=l+1;i<=n;i++)
{
if(vis[i]) continue;
if(x<c[i]) continue;
ma[i]=1;
dfs(i,x-c[i]);
ma[i]=0;
}
}
int main()
{
read(n),read(W);
for(int i=1;i<=n;i++) read(c[i]);
sort(c+1,c+n+1,cmp);
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
minx=0x7fffffff,vis[i]=1;
memset(ma,0,sizeof(ma));
memset(sta,0,sizeof(sta));
for(int j=n;j>=1;j--) if(!vis[j]){r=j;break;}
if(!r){cnt++;break;}
dfs(i,W-c[i]);
for(int j=i+1;j<=n;j++) if(sta[j]) vis[j]=1;
cnt++;
}
printf("%d\n",cnt);
}
这是一道一看就是 dp 的 dp。
朴素的直接转移是不行的,因为我们统计的是种类。
虽然走法相异,但其实只有两种情况对下面的统计造成影响,就是上一步的上一步是哪个方向的。
所以设 \(dp_{i,j,0/1}\) 为在 \((i,j)\) 这个位置,上一部是向下、向左的最小答案,依题意更新即可,时间复杂度是 \(\mathcal O(n^2)\)。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 1005
int n,m;
int dp[MAXN][MAXN][2],a[MAXN][MAXN]={0};
//0上1左
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) read(a[i][j]),dp[i][j][0]=dp[i][j][1]=0x7fffffff;
}
dp[2][1][0]=a[1][1]+a[2][1]+a[3][1]+a[1][2]+a[2][2];
dp[1][2][1]=a[1][1]+a[1][2]+a[1][3]+a[2][1]+a[2][2];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i==n&&j==m) continue;
if(i<n)
{
int x,y;
if(i>1) x=dp[i][j][0]+a[i+1][j-1]+a[i+1][j+1]+a[i+2][j];
else x=0x7fffffff;
if(j>1) y=dp[i][j][1]+a[i+1][j+1]+a[i+2][j];
else y=0x7fffffff;
dp[i+1][j][0]=min(x,min(dp[i+1][j][0],y));
}
if(j<m)
{
int x,y;
if(i>1) x=dp[i][j][0]+a[i+1][j+1]+a[i][j+2];
else x=0x7fffffff;
if(j>1) y=dp[i][j][1]+a[i-1][j+1]+a[i+1][j+1]+a[i][j+2];
else y=0x7fffffff;
dp[i][j+1][1]=min(x,min(dp[i][j+1][1],y));
}
}
}
return printf("%d\n",min(dp[n][m][0],dp[n][m][1])),0;
}
\(98.\) SP2713 GSS4 - Can you answer these queries IV
这是一类套路,今天才实现。
考虑暴力修改,线段树辅助优化复杂度套上求和。
时间复杂度是是 \(\mathcal O(\text{修改次数}\times n+n\log n)\)。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define ll long long
#define MAXN 100005
struct node
{
ll sum,rec;
}a[MAXN<<2];
int n,m;
ll t[MAXN];
int cnt=0,l,r,v;
void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum,a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}
void build(int k,int l,int r)
{
if(l==r){a[k].sum=a[k].rec=t[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
return;
}
void turn(int k,int l,int r,int x,int y)
{
if(x==y){a[k].sum=a[k].rec=sqrt(a[k].sum);return;}
int mid=(x+y)>>1;
if(a[k<<1].rec>1)
if(r<=mid) turn(k<<1,l,r,x,mid);
else if(l<=mid) turn(k<<1,l,mid,x,mid);
if(a[k<<1|1].rec>1)
if(l>mid) turn(k<<1|1,l,r,mid+1,y);
else if(r>mid) turn(k<<1|1,mid+1,r,mid+1,y);
update(k);
}
ll query(int k,int l,int r,int x,int y)
{
if(x==l&&y==r) return a[k].sum;
int mid=(x+y)>>1;
if(r<=mid) return query(k<<1,l,r,x,mid);
else if(l>mid) return query(k<<1|1,l,r,mid+1,y);
else return query(k<<1,l,mid,x,mid)+query(k<<1|1,mid+1,r,mid+1,y);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
build(1,1,n);
read(m);
printf("Case #%d:\n",++cnt);
for(int i=1;i<=m;i++)
{
read(v),read(l),read(r);
if(l>r) swap(l,r);
if(!v) turn(1,l,r,1,n);else printf("%lld\n",query(1,l,r,1,n));
}
puts("");
}
return 0;
}
可并堆模板题,这里采用配对堆实现,代码魔改自模板题。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 1000005
int n,m;
int f[MAXN],val[MAXN];
char t[3];
int x,y;
struct node
{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],cnt=0;
int le[MAXN];
void add(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
int getf(int u){return f[u]=(f[u]==u)?u:getf(f[u]);}
int merge(int u,int v)
{
int t1=getf(u),t2=getf(v);
if(t1==t2) return 0;
if(val[t1]>val[t2]||(val[t1]==val[t2]&&t1>t2)) swap(t1,t2);
f[t2]=t1,add(t1,t2);
return t1;
}
void del(int u)
{
int lst=0;
for(int i=head[u];i;i=e[i].nxt)
{
int j=e[i].to;
f[j]=j;
if(!lst) lst=j;
else lst=merge(lst,j);
}
f[u]=lst;
return;
}
int main()
{
read(n);
for(int i=1;i<=n;i++) read(val[i]),f[i]=i,le[i]=1;
read(m);
for(int i=1;i<=m;i++)
{
scanf("%s",t),read(x);
if(t[0]=='M')
{
read(y);
if(le[x]&&le[y]) merge(x,y);
}
else
{
int op=getf(x);
if(!le[x]) printf("0\n");
else
{
printf("%d\n",val[op]);
le[op]=0;
del(op);
}
}
}
return 0;
}
BSGS 板子题。
#include"algorithm"
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define ll long long
#define MAXN 200005
#define read(x) scanf("%lld",&x)
int t;
ll p,a,b;
int h;
struct num
{
ll val;
int id;
}op[MAXN];
bool cmp(num n,num m){if(n.val==m.val) return n.id>m.id;else return n.val<m.val;}
void get()
{
ll now=b;
for(int i=0;i<h;i++)
{
op[i+1].id=i,op[i+1].val=now;
now=now*a%p;
}
sort(op+1,op+h+1,cmp);
return;
}
int find()
{
ll now,mi=1;
for(int i=1;i<=h;i++) mi=mi*a%p;
now=mi;
for(int i=1;i<=h;i++)
{
int l=1,r=h,mid;
while(l<r)
{
mid=(l+r)>>1;
if(op[mid].val<now) l=mid+1;
else r=mid;
}
if(op[l].val==now) return i*h-op[l].id;
now=now*mi%p;
}
return -1;
}
void work()
{
get();
int rt=find();
if(rt<0) puts("Couldn't Produce!");
else printf("%d\n",rt);
}
int main()
{
scanf("%d",&t);
while(t--)
{
read(p),read(a),read(b);
a%=p;
if(b==1){puts("0");continue;}
if(b>=p||a==0){puts("Couldn't Produce!");continue;}
h=ceil(sqrt(p));//注意是上取整
work();
memset(op,0,sizeof(op));
}
return 0;
}
下一章精彩继续:口胡(然而有代码)<第三章>~~