D1T1小凯的疑惑\((OK)\)
D1T3逛公园\((OK)\)
D2T1奶酪\((OK)\)
D2T2宝藏\((OK)\)
话说我真的不是故意每一年都只做\(4\)道的,而是每年剩下两道都不可做...其实时间复杂度不难,但因为是字符串\(+\)大模拟就咕咕咕了.
\(D1T1\)今天重新写的时候早忘记结论了,就想说自己推,然后利用类似于完全背包的东西随便打了个表,十几分钟就发现了规律.
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
//int f[10005];
int main(){
/*for(int a=2;a<=20;++a){
for(int b=2;b<=20;++b){
cout<<a<<" "<<b<<":";
memset(f,0,sizeof(f));f[0]=1;
for(int i=a;i<=10000;++i)f[i]|=f[i-a];
for(int i=b;i<=10000;++i)f[i]|=f[i-b];
for(int i=10000;i>=2;--i)if(!f[i]){cout<<i<<endl;break;}
}
}*/
ll a=read(),b=read();
printf("%lld\n",1ll*(b-2)*a+(a-b));
return 0;
}
\(D1T2\)字符串+大模拟=咕咕咕.
\(D1T3\)做了半个上午,每次碰到这种图论题一下正图一下反图的,脑袋就不好使了(可能是一直都不太好使吧).
大概讲一下,就是先建个反图,从终点\(n\)开始跑最短路,记\(dis[i]\)表示节点\(i\)到终点\(n\)的最短路的长度.然后建正图,设\(f[i][j]\)表示从节点\(i\)到终点\(n\),当前路径长度还能够比最短路长\(j\)(即你还可以比最短路多走j的长度)的路径方案数.所以最终答案就是\(f[1][k]\).
然后考虑怎么求这个东西,\(DP\)或者记忆化搜索.记忆化搜索虽然本质上就是\(DP\),但是代码实现起来比\(DP\)简单多了.考虑当前在节点\(u\),有一条边\((u,v,w)\),那么\(f[u][k]+=f[v][k-(dis[v]+w-dis[u])]\),很好理解,因为在节点\(u\)时还剩下多余的\(k\)步可走,那么从\(u->v\)我们浪费了\(dis[v]+w-dis[u]\),所以在节点\(v\)时就只剩下\(k-(dis[v]+w-dis[u])\)多余的步了.
然后还要考虑判断无穷多解的情况,无穷多解即有一条合法路径上出现了\(0\)环,那么在记搜的同时再开一个数组\(g\)记录当前这个状态\(g[i][j]\)有没有访问过,如果这个状态之前已经访问过了,说明出现了\(0\)环
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=100005;
const int M=200005;
int n,m,K,p,a[M],b[M],c[M];
int tot,head[N],nxt[M],to[M],w[M];
int dis[N],visit[N],f[N][51],g[N][51];
inline void add(int a,int b,int c){
nxt[++tot]=head[a];head[a]=tot;
to[tot]=b;w[tot]=c;
}
inline void dij(){
memset(visit,0,sizeof(visit));
memset(dis,0x3f,sizeof(dis));
priority_queue<pair<int,int> >q;
q.push(make_pair(0,n));dis[n]=0;
while(q.size()){
int u=q.top().second;q.pop();
if(visit[u])continue;visit[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]>dis[u]+w[i]){
dis[v]=dis[u]+w[i];
q.push(make_pair(-dis[v],v));
}
}
}
}
inline int dfs(int u,int k){//记忆化搜索
if(g[u][k])return -1;//这个状态之前访问过=出现了0环
if(f[u][k])return f[u][k];//之前搜过这个状态,就不再搜了
g[u][k]=1;//标记该状态访问过
f[u][k]=(u==n);//初始化
for(int i=head[u];i;i=nxt[i]){
int v=to[i],dist=dis[v]+w[i]-dis[u];
if(k-dist>=0){
int cnt=dfs(v,k-dist);
if(cnt==-1)return f[u][k]=-1;
else f[u][k]=(f[u][k]+cnt)%p;
}
}
g[u][k]=0;//回溯
return f[u][k];
}
int main(){
int T=read();
while(T--){
n=read();m=read();K=read();p=read();
tot=0;memset(head,0,sizeof(head));
for(int i=1;i<=m;++i){
a[i]=read();b[i]=read();c[i]=read();
add(b[i],a[i],c[i]);
}
dij();//反向图最短路
tot=0;memset(head,0,sizeof(head));//初始化建正图
for(int i=1;i<=m;++i)add(a[i],b[i],c[i]);
memset(f,0,sizeof(f));memset(g,0,sizeof(g));
printf("%d\n",dfs(1,K));
}
return 0;
}
\(D2T1\)方法好像挺多的,我觉得我能够一下想到的,应该也是最容易想到的方法吧.就是\(BFS\),初始时把每个能从\(z=0\)钻入的洞丢进队列,每次暴力枚举扩展相连通的洞,找到一个能够到达\(z=h\)的洞即可.
然后我自己\(WA\)了几次是因为几何数学没有学好???对于一个洞的球心\((x,y,z)\),如果它要能够到达\(z=0/h\),那么只要\(dist((x,y,z),(0,0,0/h))<=r\)即可,而我们\(BFS\)扩展的时候,对于两个洞的球心\((x1,y1,z1),(x2,y2,z2)\),这两个洞要连通只要保证\(dist<=2*r\).(之前没考虑好,全都是写的小于等于\(2r\),竟然还有\(70\)分)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=1005;
int n,h,r,visit[N];
struct dong{int x,y,z;}a[N];
inline double dis(int i,int j){
return (double)sqrt(1.0*(a[i].x-a[j].x)*(a[i].x-a[j].x)+1.0*(a[i].y-a[j].y)*(a[i].y-a[j].y)+1.0*(a[i].z-a[j].z)*(a[i].z-a[j].z));
}
int main(){
int T=read();
while(T--){
memset(visit,0,sizeof(visit));queue<int>q;
n=read();h=read();r=read();a[0].x=0;a[0].y=0;a[0].z=0;
for(int i=1;i<=n;++i){
a[i].x=read();a[i].y=read();a[i].z=read();
if((double)sqrt(1.0*a[i].z*a[i].z)<=(double)r)q.push(i),visit[i]=1;
}
a[n+1].x=0;a[n+1].y=0;a[n+1].z=h;int bj=0;
while(q.size()){
int u=q.front();q.pop();
if((double)sqrt(1.0*(a[u].z-h)*(a[u].z-h))<=(double)r){bj=1;break;}
for(int i=1;i<=n;++i){
if(visit[i])continue;
if(dis(u,i)<=(double)2.0*r){q.push(i);visit[i]=1;}
}
}
if(bj)puts("Yes");
else puts("No");
}
return 0;
}
\(D2T2\) \(n<=12\)很明显的状压,然后刚开始状态设错,搞了半个上午都没做出来.只好去看题解了
因为\(DP\)扩展状态的时候 会发现贡献与当前这个节点距离根节点(最开始选的那一个宝藏屋)有关,所以考虑把这个设入状态.
设\(f[i][j]\)表示当前考虑好了的点集是\(j\),扩展到了第\(i\)层(即点集\(j\)中距离根节点最深的节点的深度是\(i\))是的最小花费.
\(f[i][j|k]=min(f[i][j|k],f[i-1][j]+dist[j][k]*(i-1))\).\(j,k\)是两个互不相交的集合,\(dist[j][k]\)表示使得\(j,k\)这两个集合连通的最短距离.
我们可以先预处理\(dis[i][j]\)表示点\(i\)到点集\(j\)的最短距离(点\(i\)与点集\(j\)中的点直接连通).然后再通过\(dis[i][j]\)求出\(dist[i][j]\).注意到我们需要枚举与一个集合互不相交的所有集合,其实就是该集合的补集的所有子集.
我为了卡常,把所有的\(memset\)改成了循环,所以代码可能很丑陋.为什么最近做的几道\(NOIP\)题都需要卡常啊,本题卡常前总用时\(5.45s\),\(T\)了\(4\)个点,卡常后总用时\(3.53s\),\(AC\).区别还是挺大的啊.
本题因为\(NOIP\)的数据水,还有各种奇怪又美妙的做法,时间复杂度能够吊打状压.
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define rg register
#define ll long long
using namespace std;
inline int read(){
rg int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*o;
}
const int inf=2e9;
int w[15][15],dis[15][1<<12],dist[1<<12][1<<12],f[15][1<<12];
int main(){
rg int n=read(),m=read();
for(rg int i=1;i<=n;++i)
for(rg int j=1;j<=n;++j)w[i][j]=inf;
for(rg int i=1;i<=m;++i){
rg int a=read(),b=read(),c=read();
if(c<w[a][b])w[a][b]=c,w[b][a]=c;//显然有重边
}
rg int S=(1<<n)-1;
for(rg int i=1;i<=n;++i)
for(rg int j=1;j<=S;++j)dis[i][j]=inf;
for(rg int i=1;i<=n;++i){//预处理dis数组
for(rg int j=1;j<=S;++j){
if(!(j&(1<<(i-1)))){
for(rg int k=1;k<=n;++k)
if(j&(1<<(k-1)))dis[i][j]=min(dis[i][j],w[k][i]);
}
}
}
for(rg int i=1;i<=S;++i){//预处理dist[i][j]
for(rg int j=i^S;j;j=(j-1)&(i^S)){//枚举子集
for(rg int k=1;k<=n;++k){
if((j&(1<<(k-1)))){
if(dis[k][i]==inf){dist[i][j]=0;break;}
//只要集合j中有一个点无法到达集合$i$,就说明这两个集合不能连通
//因为这里的连通都是建立在直接相连的意义下的,这样才能保证后面一层一层更新的正确性
dist[i][j]+=dis[k][i];
}
}
}
}
rg int ans=2e9;
for(rg int st=1;st<=n;++st){//枚举根节点
for(rg int i=1;i<=n;++i)
for(rg int j=0;j<=S;++j)f[i][j]=inf;
f[1][1<<(st-1)]=0;//初始化
for(rg int i=2;i<=n;++i){
for(rg int j=1;j<=S;++j){
for(rg int k=j^S;k;k=(k-1)&(j^S)){
if(dist[j][k])f[i][j|k]=min(f[i][j|k],f[i-1][j]+dist[j][k]*(i-1));
}
}
}
for(rg int i=1;i<=n;++i)if(f[i][S]<ans)ans=f[i][S];
}
printf("%d\n",ans);
return 0;
}