[ NOIP 2009 ] TG
\(\\\)
\(\#A\) \(Spy\)
给出两个长度均为\(N\)相同的样例串,建立第一个串各个字符向第二个串对应位置字符的映射,并用映射转换给出的长度为\(M\)第三个串,输入保证只有大写字符。
若出现\(26\)个大写字符未建立完整,映射一些字符映射所得字符相同或同一个字符建立多个映射,则视为不合法,输出\(“failed”\)。否则,输出转换后的串。
- \(N,M\in [1,100]\)
- 字符串处理题,开三个数组分别记录两个样例串每个字符是否出现,以即映射。
- 在建立映射时,判断已有映射与先前建立的映射是否相同、新建映射所得字符是否被作为映射答案出现过。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 30
#define M 110
#define R register
using namespace std;
bool v1[N],v2[N];
int to[N],now[M],p;
int main(){
char c=getchar();
while(!isupper(c)) c=getchar();
while(isupper(c)){
now[++now[0]]=c-'A'+1;
v1[c-'A'+1]=1; c=getchar();
}
for(R int i=1;i<=26;++i) if(!v1[i]){puts("Failed");return 0;}
while(!isupper(c)) c=getchar();
while(isupper(c)){
int x=now[++p];
if(to[x]){if('A'+to[x]-1!=c){puts("Failed");return 0;}}
else if(v2[c-'A'+1]){puts("Failed");return 0;}
else to[x]=c-'A'+1;
v2[c-'A'+1]=1; c=getchar();
}
while(!isupper(c)) c=getchar();
while(isupper(c)){
putchar((char)'A'+to[c-'A'+1]-1);
c=getchar();
}
return 0;
}
\(\\\)
\(\#B\) \(Son\)
\(N\)组数据,每组给出四个正整数\(a_0,a_1,b_0,b_1\),求满足\(gcd(x,a_0)=a_1,lcm[x,b_0]=b_1\)的\(x\)的个数。
- \(N\in [1,200]\),\(a_0,a_1,b_0,b_1\in [1,2\times 10^9]\)
-
由 \(gcd(x,a_0)=a_1\) 得 \(gcd(\frac{x}{a_1},\frac{a_0}{a_1})=1\)
-
由 \(lcm[x,b_0]=b_1\) 得 \(gcd(x,b_0)=\frac{x\times b_0}{b_1}\),有 \(gcd(\frac{x}{gcd(x,b_0)},\frac{b_0}{gcd(x,b_0)})=gcd(\frac{b_1}{b_0},\frac{b_1}{x})=1\)
-
由上知合法的\(x\)一定是\(a_1\)的倍数,一定是\(b_1\)的因数,所以得到一个做法,试除法\(\Theta(\sqrt b_1)\)求\(b_1\)因数,判断是否为\(a_1\)因数,再进一步判断推出的两个条件是否成立,总复杂度\(\Theta(N\sqrt {2\times10^9}log(2\times10^9))\approx\Theta(8\times 10^7)\)
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define R register
#define gc getchar
using namespace std;
inline int rd(){
int x=0; char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x;
}
inline int gcd(int x,int y){return y?gcd(y,x%y):x;}
inline void work(){
int a0=rd(),a1=rd(),b0=rd(),b1=rd();
int ans=0,lim=sqrt(b1); a0/=a1; b0=b1/b0;
for(R int x=1;x<=lim;++x)
if(!(b1%x)){
if(!(x%a1)&&gcd(x/a1,a0)==1&&gcd(b1/x,b0)==1)++ans;
if(x*x==b1)continue;
int k=b1/x;
if(!(k%a1)&&gcd(k/a1,a0)==1&&gcd(b1/k,b0)==1)++ans;
}
printf("%d\n",ans);
}
int main(){
int t=rd();
while(t--)work();
return 0;
}
\(\\\)
\(\#C\) \(Trade\)
给出一个\(N\)个节点\(M\)条边的图,边有向边和无向边两种,每个点有点权\(V_i\)。
求出一条从\(1\)号节点到\(N\)号节点的路径,使得在路径上有两个点\(u,v\),保证\(u\)在\(v\)之前出现,且\(V_v-V_u\)的值最大,输出该值即可。
- \(N\in [1,10^5]\),\(M\in [1,5\times 10^5]\),\(V_i\in [1,100]\)
最短路:
-
考虑无向边拆成两条有向边,跑一个以\(1\)为源的单源最短路,统计从\(1\)号节点到该节点路径上最小点权,更新变成\(dismin[v]=min(dismin[u],V_v)\),该值代表若选择从\(1\)号节点到这个节点的路径,选这个点作为\(u\)点。
-
然后就是统计从这个点到\(n\)号节点路径上最大点权,代表选这个点作为\(u\)点,统计方式可以考虑建一个反图,跑以\(n\)为源的单元最短路,统计从\(n\)号节点到该节点路径上的最大点权,也就是原图中从该节点到\(n\)号节点的最大点权,更新变成\(dismax[v]=max(dismax[u],V_v)\)。
-
对每个点用\(dismax[i]-dismin[i]\)更新答案即可。
-
注意到题目所给图较稀疏,\(SPFA\)运行效率比堆优化\(Dijkstra\)实测更优秀一些。
\(SPFA\)版本
#include<cmath> #include<queue> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define M 1000010 #define R register #define gc getchar using namespace std; inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } int hd1[N],hd2[N],tot1,tot2; struct edge{int to,nxt;}e1[M],e2[M]; inline void add1(int u,int v){ e1[++tot1].to=v; e1[tot1].nxt=hd1[u]; hd1[u]=tot1; } inline void add2(int u,int v){ e2[++tot2].to=v; e2[tot2].nxt=hd2[u]; hd2[u]=tot2; } bool vis1[N],vis2[N]; int n,m,ans,mn[N],mx[N],val[N]; queue<int> q1,q2; inline void SPFA1(){ memset(mn,0x3f,sizeof(mn)); mn[1]=val[1]; q1.push(1); while(!q1.empty()){ int u=q1.front(); q1.pop(); vis1[u]=0; for(R int i=hd1[u],v;i;i=e1[i].nxt) if(mn[v=e1[i].to]>min(val[v],mn[u])){ mn[v]=min(val[v],mn[u]); if(!vis1[v]) q1.push(v); vis1[v]=1; } } } inline void SPFA2(){ mx[n]=val[n]; q2.push(n); while(!q2.empty()){ int u=q2.front(); q2.pop(); vis2[u]=0; for(R int i=hd2[u],v;i;i=e2[i].nxt) if(mx[v=e2[i].to]<max(val[v],mx[u])){ mx[v]=max(val[v],mx[u]); if(!vis2[v]) q2.push(v); vis2[v]=1; } } } int main(){ n=rd(); m=rd(); for(R int i=1;i<=n;++i) val[i]=rd(); for(R int i=1,u,v,w;i<=m;++i){ u=rd(); v=rd(); w=rd(); add1(u,v); add2(v,u); if(w==2){add1(v,u); add2(u,v);} } SPFA1(); SPFA2(); for(R int i=1;i<=n;++i) ans=max(ans,mx[i]-mn[i]); printf("%d\n",ans); return 0; }
\(Dijkstra\)版本
#include<cmath> #include<queue> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define M 1000010 #define R register #define gc getchar using namespace std; inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } int hd1[N],hd2[N],tot1,tot2; struct edge{int to,nxt;}e1[M],e2[M]; inline void add1(int u,int v){ e1[++tot1].to=v; e1[tot1].nxt=hd1[u]; hd1[u]=tot1; } inline void add2(int u,int v){ e2[++tot2].to=v; e2[tot2].nxt=hd2[u]; hd2[u]=tot2; } bool vis1[N],vis2[N]; int n,m,ans,mn[N],mx[N],val[N]; priority_queue<pair<int,int> > q1,q2; inline void dij1(){ memset(mn,0x3f,sizeof(mn)); mn[1]=val[1]; q1.push(make_pair(-val[1],1)); while(!q1.empty()){ int u=q1.top().second; q1.pop(); if(vis1[u]) continue; vis1[u]=1; for(R int i=hd1[u],v;i;i=e1[i].nxt) if(mn[v=e1[i].to]>min(val[v],mn[u])){ mn[v]=min(val[v],mn[u]); q1.push(make_pair(-mn[v],v)); } } } inline void dij2(){ mx[n]=val[n]; q2.push(make_pair(val[n],n)); while(!q2.empty()){ int u=q2.top().second; q2.pop(); if(vis2[u]) continue; vis2[u]=1; for(R int i=hd2[u],v;i;i=e2[i].nxt) if(mx[v=e2[i].to]<max(val[v],mx[u])){ mx[v]=max(val[v],mx[u]); q2.push(make_pair(mx[v],v)); } } } int main(){ n=rd(); m=rd(); for(R int i=1;i<=n;++i) val[i]=rd(); for(R int i=1,u,v,w;i<=m;++i){ u=rd(); v=rd(); w=rd(); add1(u,v); add2(v,u); if(w==2){add1(v,u); add2(u,v);} } dij1(); dij2(); for(R int i=1;i<=n;++i) ans=max(ans,mx[i]-mn[i]); printf("%d\n",ans); return 0; }
\(Tarjan+DFS\):
-
同样无向边拆成两条有向边,\(Tarjan\)找\(SCC\)缩点,记录每个\(SCC\)内最大点权和最小点权,因为\(SCC\)内可以一直转圈,所以最大最小点权在这个\(SCC\)内一定可以得到。建新图时注意,若为节省空间使用同一个邻接表,需清空\(head\)数组。
-
考虑到缩点后一些路径不会经过\(1\)号或\(N\)号节点,所以不能拓扑排序,只能记忆化搜索。记\(f_i\)表示\(i\)号结点到\(N\)号节点的路径上的最大值。为了防止路径不可由\(1\)号节点引出,可以选择只从\(1\)号节点\(DFS\)。防止路径不可到达\(N\)号节点,选择只有到达\(N\)号节点所属\(SCC\)时才更新\(f\)数组,其余时由其他可达点更新当前点。
-
若\(DFS\)所有可达点之后\(f_i\)不是默认值则证明其可达\(N\)号节点,则可进一步用\(sccmax_{i}\)更新\(f_i\),并进一步用\(f_i-sccmin_i\)更新答案,代表在这个\(SCC\)买入,在从此开始后续\(SCC\)中最大点卖出。
#include<cmath> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define M 500010 #define R register #define gc getchar #define top stk[0] using namespace std; bool vis[N]; int n,m,tot,ans,hd[N],val[N],f[N]; int num,cnt,low[N],dfn[N],bl[N],stk[N],mn[N],mx[N]; struct adjlist{int to,nxt;}e[M<<1]; struct Edge{int x,y;}edge[M<<1]; inline void add(int u,int v){ e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot; } inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } inline void tarjan(int u){ stk[++top]=u; vis[u]=1; dfn[u]=low[u]=++cnt; for(R int i=hd[u],v;i;i=e[i].nxt) if(!dfn[v=e[i].to]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(vis[v]) low[u]=min(low[u],dfn[v]); if(dfn[u]==low[u]){ ++num; do{ bl[stk[top]]=num; vis[stk[top]]=0; mn[num]=min(mn[num],val[stk[top]]); mx[num]=max(mx[num],val[stk[top--]]); }while(stk[top+1]!=u); } } inline bool cmp(Edge a,Edge b){return (a.x==b.x)?(a.y<b.y):a.x<b.x;} inline void dfs(int u){ vis[u]=1; if(u==bl[n]) f[u]=max(f[u],mx[u]); for(R int i=hd[u],v;i;i=e[i].nxt){ if(!vis[v=e[i].to]) dfs(v); f[u]=max(f[u],f[v]); } if(f[u]) f[u]=max(f[u],mx[u]); ans=max(ans,f[u]-mn[u]); } int main(){ n=rd(); m=rd(); for(R int i=1;i<=n;++i) val[i]=rd(); for(R int i=1,u,v;i<=m;++i){ u=rd(); v=rd(); add(u,v); if(rd()==2) add(v,u); } memset(mn,0x3f,sizeof(mn)); tarjan(1); int tmp=0; tot=0; for(R int i=1;i<=n;++i) for(R int j=hd[i],v;j;j=e[j].nxt) if(bl[i]!=bl[v=e[j].to]){edge[++tmp].x=bl[i];edge[tmp].y=bl[v];} sort(edge+1,edge+1+tmp,cmp); memset(hd,0,sizeof(hd)); for(R int i=1;i<=tmp;++i) if(edge[i].x!=edge[i-1].x||edge[i].y!=edge[i-1].y) add(edge[i].x,edge[i].y); dfs(bl[1]); printf("%d\n",ans); return 0; }
\(\\\)
\(\#D\) \(Sudoku\)
给出一个未完成的九宫数独,并定义每一个位置的权值:
求保证填数合法的前提下,完成这个数独所能得到的权值和最大是多少。
- 数据保证已填入的数不少于\(24\)个。
\(DFS:\)
- 扫描一遍整个数独,记录下所有待填数的位置,同时记录该位置可能填入的数字,按顺序搜索到合法解即可。
- 一个看起来正确的优化是按照可能填入的数字个数排序,或按照所在行\(/\)列\(/\)宫剩余位置个数排序,这样在搜索的时候能够使搜索树上的较低层节点数尽可能地优秀。
- 实测不开\(\text O 2\)最好的状态下只会超时\(1\)个点。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define R register
#define gc getchar
using namespace std;
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
bool h[10][10],l[10][10],g[10][10];
int num[10][10],bl[10][10];
int ans=-1,numh[10],numl[10],numg[10];
int tot,val[10][10]={
{0,0,0,0,0,0,0,0,0,0},
{0,6,6,6,6,6,6,6,6,6},
{0,6,7,7,7,7,7,7,7,6},
{0,6,7,8,8,8,8,8,7,6},
{0,6,7,8,9,9,9,8,7,6},
{0,6,7,8,9,10,9,8,7,6},
{0,6,7,8,9,9,9,8,7,6},
{0,6,7,8,8,8,8,8,7,6},
{0,6,7,7,7,7,7,7,7,6},
{0,6,6,6,6,6,6,6,6,6}};
struct points{int x,y,w;}p[90];
inline void add(int x,int y){
p[++tot].x=x; p[tot].y=y; p[tot].w=0;
for(R int i=1;i<=9;++i){
if(h[x][i]||l[y][i]||g[bl[x][y]][i])continue;
else ++p[tot].w;
}
}
inline bool cmp(points x,points y){
if(numl[x.y]!=numl[y.y]) return numl[x.y]<numl[y.y];
else if(numh[x.x]!=numh[y.x]) return numh[x.x]<numh[y.x];
else if(g[bl[x.x][x.y]]!=g[bl[y.x][y.y]]) return g[bl[x.x][x.y]]<g[bl[y.x][y.y]];
else return x.w<y.w;
}
inline int calc(){
int res=0;
for(R int i=1;i<=9;++i)
for(R int j=1;j<=9;++j)
res+=val[i][j]*num[i][j];
return res;
}
inline void dfs(int t,int sum){
if(t==tot+1){ans=max(ans,sum);return;}
int x=p[t].x,y=p[t].y;
for(R int i=1;i<=9;++i){
if(h[x][i]||l[y][i]||g[bl[x][y]][i]) continue;
else{
h[x][i]=l[y][i]=g[bl[x][y]][i]=1;
dfs(t+1,sum+i*val[x][y]);
h[x][i]=l[y][i]=g[bl[x][y]][i]=0;
}
}
}
int main(){
for(R int i=1;i<=9;++i)
for(R int j=1;j<=9;++j){
h[i][num[i][j]=rd()]=1;
if(num[i][j]==0) ++numh[i];
}
for(R int j=1;j<=9;++j)
for(R int i=1;i<=9;++i){
l[j][num[i][j]]=1;
if(num[i][j]==0) ++numl[j];
}
for(R int i=1;i<=7;i+=3){
for(R int j=1;j<=3;++j)
for(R int k=i;k<=i+2;++k){
g[i][num[j][k]]=1;bl[j][k]=i;
if(num[j][k]==0) ++numg[i];
}
for(R int j=4;j<=6;++j)
for(R int k=i;k<=i+2;++k){
g[i+1][num[j][k]]=1;bl[j][k]=i+1;
if(num[j][k]==0) ++numg[i+1];
}
for(R int j=7;j<=9;++j)
for(R int k=i;k<=i+2;++k){
g[i+2][num[j][k]]=1;bl[j][k]=i+2;
if(num[j][k]==0) ++numg[i+2];
}
}
int tmp=0;
for(R int i=1;i<=9;++i)
for(R int j=1;j<=9;++j)
if(!num[i][j]) add(i,j);
else tmp+=val[i][j]*num[i][j];
sort(p+1,p+1+tot,cmp);
dfs(1,tmp);
printf("%d\n",ans);
return 0;
}
\(Dancing\ Links\ X:\)
-
基本的数独转精确覆盖问题及\(Dancing\ Links\ X\)解法可以参考:\(Dancing\ Links\ X\)
-
与基本的数独做法相同,但在找到答案之后不直接\(return\),而要搜完所有的状态。
-
在计算过程中\(dance\)函数可以传一个当前权值和的参数,因为每一行只代表一个位置的一个方案,所以具体的实现过程可以对每一行先绑定权值,然后选择行的时候答案就可以直接累加了。
#include<cmath>
#include<vector>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 750
#define M 350
#define S 247500
#define R register
#define gc getchar
using namespace std;
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
inline int calc(int x,int y,int k){return x*81+y*9+k+1;}
inline void restore(int ans,int &x,int &y,int &k){
k=(--ans)%9; ans/=9; y=ans%9; x=(ans/9)%9;
}
vector<int> res;
int t,n,m,maxr=729,maxc=324,num[10][10];
const int score[9][9]={
{6,6,6,6,6,6,6,6,6},
{6,7,7,7,7,7,7,7,6},
{6,7,8,8,8,8,8,7,6},
{6,7,8,9,9,9,8,7,6},
{6,7,8,9,10,9,8,7,6},
{6,7,8,9,9,9,8,7,6},
{6,7,8,8,8,8,8,7,6},
{6,7,7,7,7,7,7,7,6},
{6,6,6,6,6,6,6,6,6}
};
struct dlx{
int n,m,tot,ans=-1,s[M],h[N];
int u[S],d[S],l[S],r[S],row[S],col[S];
inline void reset(int _n,int _m){
n=_n; m=_m;
for(R int i=0;i<=m;++i){l[i]=i-1; r[i]=i+1; u[i]=d[i]=i;}
l[0]=m; r[m]=0; tot=m;
memset(s,0,sizeof(s));
memset(h,-1,sizeof(h));
}
inline void insert(int x,int y,int k){
row[++tot]=k; col[tot]=y; ++s[y];
u[tot]=u[y]; d[tot]=y;
d[u[tot]]=tot; u[d[tot]]=tot;
if(h[x]==-1){h[x]=tot; l[tot]=tot; r[tot]=tot;}
else{
l[tot]=l[h[x]]; r[tot]=h[x];
r[l[tot]]=tot; l[r[tot]]=tot;
}
}
inline void remove(int y){
r[l[y]]=r[y]; l[r[y]]=l[y];
for(R int i=d[y];i!=y;i=d[i])
for(R int j=r[i];j!=i;j=r[j]){
u[d[j]]=u[j]; d[u[j]]=d[j]; --s[col[j]];
}
}
inline void restore(int y){
for(R int i=d[y];i!=y;i=d[i])
for(R int j=r[i];j!=i;j=r[j]){
u[d[j]]=j; d[u[j]]=j; ++s[col[j]];
}
r[l[y]]=y; l[r[y]]=y;
}
inline void dance(int sum){
if(r[0]==0){ans=max(ans,sum);return;}
int y=r[0];
for(R int i=r[0];i!=0;i=r[i]) if(s[i]<s[y]) y=i;
remove(y);
for(R int i=d[y];i!=y;i=d[i]){
for(R int j=r[i];j!=i;j=r[j]) remove(col[j]);
dance(sum+row[i]);
for(R int j=l[i];j!=i;j=l[j]) restore(col[j]);
}
restore(y); return;
}
}dlx;
int main(){
dlx.reset(maxr,maxc);
for(R int i=0;i<=8;++i)
for(R int j=0;j<=8;++j) num[i][j]=rd();
for(R int i=0;i<=8;++i)
for(R int j=0;j<=8;++j)
for(R int k=0;k<=8;++k)
if(num[i][j]==0||num[i][j]==k+1){
int x=calc(i,j,k);
dlx.insert(x,calc(0,i,j),score[i][j]*(k+1));
dlx.insert(x,calc(1,i,k),score[i][j]*(k+1));
dlx.insert(x,calc(2,j,k),score[i][j]*(k+1));
dlx.insert(x,calc(3,(i/3)*3+j/3,k),score[i][j]*(k+1));
}
dlx.dance(0);
printf("%d\n",dlx.ans);
return 0;
}