老年退役选手来过时口胡一波
考场上的普遍做法就是打表吧,规律还是很好找的
很容易看出答案是a*b-a-b
比16年的D1T2不知道良心多少。。。
模拟就行了,但还是需要一定的比赛经验的,写完后多拿自制数据试一试,高分还是很容易拿的
一开始我只会做DAG的DP,不知道怎么处理环的问题,想用tarjan缩点但发现没有用,后来发现直接记忆化搜索下去就可以了,f[i][j]表示DP到第i个点,此时最少走的额外路径长为j,因为状态不会重复,-1的情况判断0环上的点是否在最短路上就行了
dfs一遍判断0环就可以了,判断是否在最短路上可以正反做两次SPFA
时间复杂度 O(T(最短路+km)) O ( T ( 最 短 路 + k m ) )
代码:
#include
#include
#include
#include
using namespace std;
int T,n,m,k,p,tot,goal;
int first[2][100005],dis[2][100005],f[100005][52],sta[100005];
char tim[100005][52],ok[100005];
bool vis[100005],flag;
struct edge{
int v,w,next;
}e[2][200005];
int in()
{
int t=0;char ch=getchar();
while (ch>'9'||ch<'0') ch=getchar();
while (ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
return t;
}
void add(int x,int y,int z)
{
e[0][++tot].v=y;e[0][tot].w=z;e[0][tot].next=first[0][x];first[0][x]=tot;
e[1][tot].v=x;e[1][tot].w=z;e[1][tot].next=first[1][y];first[1][y]=tot;
}
int up(int x,int y){return ((x+y)%p+p)%p;}
queue<int>q;
void SPFA(int r)
{
int now=(r?n:1);
memset(dis[r],63,sizeof(dis[r]));
dis[r][now]=0;
for (q.push(now);!q.empty();q.pop())
{
now=q.front();
for (int i=first[r][now];i;i=e[r][i].next)
if (dis[r][e[r][i].v]>dis[r][now]+e[r][i].w)
{
dis[r][e[r][i].v]=dis[r][now]+e[r][i].w;
if (!vis[e[r][i].v]) vis[e[r][i].v]=1,q.push(e[r][i].v);
}
vis[now]=0;
}
}
int dp(int x,int D)
{
if (dis[1][x]+D-goal>k) return 0;
if (tim[x][dis[1][x]+D-goal]==T) return f[x][dis[1][x]+D-goal];
tim[x][dis[1][x]+D-goal]=T;
f[x][dis[1][x]+D-goal]=(x==n);
for (int v,i=first[0][x];i;i=e[0][i].next)
{
v=e[0][i].v;
f[x][dis[1][x]+D-goal]=up(f[x][dis[1][x]+D-goal],dp(v,D+e[0][i].w));
}
return f[x][dis[1][x]+D-goal];
}
void dfs(int x)
{
ok[x]=T;
sta[++sta[0]]=x;
vis[x]=1;
for (int v,i=first[0][x];i;i=e[0][i].next)
if (e[0][i].w==0)
{
v=e[0][i].v;
if (ok[v]!=T) dfs(v);
else if (vis[v]&&dis[0][v]+dis[1][v]<=k+goal)
flag=1;
}
--sta[0];
vis[x]=0;
}
void work()
{
n=in();m=in();k=in();p=in();
tot=0;flag=0;
for (int i=1;i<=n;++i) first[0][i]=first[1][i]=0;
for (int u,v,w,i=1;i<=m;++i)
u=in(),v=in(),w=in(),
add(u,v,w);
SPFA(0);
SPFA(1);
goal=dis[0][n];
for (int i=1;i<=n;++i)
if (ok[i]!=T) dfs(i);
if (flag) puts("-1");
else printf("%d\n",dp(1,0));
}
main(){for (T=in();T;--T) work();}
模拟+并查集显然,注意距离的平方可以爆long long,自行处理
有趣的状压DP,延续了16年的风格,感觉比16年的D2T3难一点?
一开始我多加了一维状态,发现答案不对,只能改成f[D][S]表示最大深度为D,n个点的状态为S,转移时枚举未转移点的集合S’就可以了,实际程序中(枚举完起点后)先枚举S,然后预处理S’连到S的代价,再枚举D
时间复杂度 O(n23n) O ( n 2 3 n )
#include
#include
#include
#include
using namespace std;
int n,m;
int dis[13][13],f[13][1<<12],w[1<<12],bit[1<<12],p[13];
stack<int>S;
int cal(int id)
{
int t=1e9;
for (int i=1;i<=p[0];++i) t=min(t,dis[p[i]][id]);
return t;
}
main()
{
scanf("%d%d",&n,&m);
memset(dis,63,sizeof(dis));
for (int x,y,z,i=1;i<=m;++i)
scanf("%d%d%d",&x,&y,&z),
dis[x][y]=dis[y][x]=min(dis[x][y],z);
for (int i=1;i<=n;++i) dis[i][i]=0;
for (int j=1,i=1;i<=n;++i,j<<=1) bit[j]=i;
int ans=1e9,cop;
for (int s=1;s<=n;++s)
{
memset(f,63,sizeof(f));
f[0][1<<s-1]=0;
for (int k,state=(1<<s-1);state<(1<state)
if (state&(1<<s-1))
{
p[0]=0;
cop=((1<1)^state;
for (int i=cop;i;i=(cop&i-1)) S.push(i);
k=state;
for (;k;k^=(k&-k)) p[++p[0]]=bit[k&-k];
for (;!S.empty();S.pop())
k=S.top(),
w[k]=(w[k^(k&-k)]<1e9?w[k^(k&-k)]+cal(bit[k&-k]):1e9);
for (int D=0;Dfor (int sub=cop;sub;sub=(cop&sub-1))
if (w[sub]<1e9)
f[D+1][state|sub]=min(f[D+1][state|sub],f[D][state]+w[sub]*(D+1));
}
for (int D=0;D1<1]);
}
printf("%d\n",ans);
}
感觉难度比不上16年的D1T2?
n,m小的时候模拟,q小的时候枚举之前询问的影响,x=1时只影响第一行和第m列组成的倒”L”型,实际上变成了序列问题,线段树或平衡树随便做,这样就有50~70了
想想16年D1T2的树链剖分
然后我就很咸鱼地没有考虑到建n+1个线段树来维护每一行和最后一列。。
运用动态开点的权值线段树,起始状态1~m-1(n)+q上均有1个,每次修改相当于在对应行(列)的线段树上删去一个数,拿vector去存大于m-1(n)的数对应的编号
时间复杂度 O(qlog(max(n,m)+q)) O ( q log ( max ( n , m ) + q ) )
#include
#include
#define LL long long
#define M 300005
using namespace std;
int in()
{
int t=0;char ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch>='0'&&ch<='9') t=t*10+ch-48,ch=getchar();
return t;
}
int n,m,q,cnt;
int root[M],ls[M*19],rs[M*19],del_num[M*19];
vector val[M];
int get(int &rt,int l,int r,int k)
{
if (!rt) rt=++cnt;
++del_num[rt];
if (l==r) return r;
int mid=l+r>>1,sz=mid-l+1-del_num[ls[rt]];
if (sz>=k) return get(ls[rt],l,mid,k);
else return get(rs[rt],mid+1,r,k-sz);
}
main()
{
n=in();m=in();q=in();
LL ans;
for (int x,y,t,i=1;i<=q;++i)
{
x=in();y=in();
if (y==m)
{
t=get(root[n+1],1,q+n,x);
if (t<=n) ans=1LL*t*m;
else ans=val[n+1][t-n-1];
printf("%lld\n",ans);
val[n+1].push_back(ans);
}
else
{
t=get(root[x],1,q+m,y);
if (t1LL*x*m+t-m;
else ans=val[x][t-m];
printf("%lld\n",ans);
val[n+1].push_back(ans);
t=get(root[n+1],1,q+n,x);
if (t<=n) ans=1LL*t*m;
else ans=val[n+1][t-n-1];
val[x].push_back(ans);
}
}
}