首先位数相同的点之间的边随便连。此时每两种位数的点之间的边数和每种位数的连通块数。
如果有解则一定存在一种方案,是将每种位数拿一个点出来,连成一个生成树,然后把其它的点连到生成树上的点。
爆搜生成树的形态,然后用网络流求匹配判断是否存在方案。
#include
#include
#include
#include
#include
#include
#define PII pair
#define PB push_back
#define MP make_pair
#define fir first
#define sec second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
int S,T,ncnt;
namespace Flow {
const int N=210;
int head[N],cur[N],dep[N];
struct ed { int to,next,f; };
vector<ed> e;
void init() { memset(head,-1,sizeof(head)); e.clear(); }
void ad(int x,int y,int f) {
e.PB((ed){y,head[x],f}); head[x]=e.size()-1;
e.PB((ed){x,head[y],0}); head[y]=e.size()-1;
}
queue<int> que;
bool bfs() {
for(int i=1;i<=ncnt;++i) dep[i]=-1,cur[i]=head[i];
while(!que.empty()) que.pop();
que.push(S),dep[S]=0;
while(!que.empty()) {
int u=que.front(); que.pop();
if(u==T) return 1;
for(int k=head[u];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to;
if(dep[v]==-1) {
dep[v]=dep[u]+1;
que.push(v);
}
}
}
return 0;
}
int dfs(int u,int f) {
if(u==T||!f) return f; int tmp,ret=0;
for(int &k=cur[u];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to;
if(dep[v]==dep[u]+1&&(tmp=dfs(v,min(e[k].f,f)))) {
e[k].f-=tmp,e[k^1].f+=tmp;
f-=tmp,ret+=tmp;
if(!f) break;
}
}
return ret;
}
int work() { int ans=0; while(bfs()) ans+=dfs(S,1e9); return ans; }
void get(int mp[][210]) {
for(int i=1;i<=ncnt;++i)
for(int k=head[i];~k;k=e[k].next)
mp[i][e[k].to]=e[k].f;
}
}
int m,n;
int E[10][10],num[10];
int id1[10][10],id2[10];
vector<PII> ansE;
bool sol() {
Flow::init();
for(int i=0;i<m;++i)
for(int j=0;j<i;++j)
if(E[i][j]) {
Flow::ad(S,id1[i][j],E[i][j]);
Flow::ad(id1[i][j],id2[i],E[i][j]);
Flow::ad(id1[i][j],id2[j],E[i][j]);
}
for(int i=0;i<m;++i) Flow::ad(id2[i],T,num[i]-1);
return Flow::work()==n-m-ansE.size();
}
int pru[10];
int vis[10],buc[10];
int G[10][10];
int flow[210][210],pcnt[10][10];
vector<int> node[10];
void getpru() {
memcpy(E,G,sizeof(G));
for(int i=0;i<m;++i) vis[i]=buc[i]=0;
for(int i=0;i<m-2;++i) buc[pru[i]]++;
for(int i=0;i<m-2;++i) {
int p=0;
while(buc[p]||vis[p]) p++;
E[p][pru[i]]--,E[pru[i]][p]--;
vis[p]=1;
buc[pru[i]]--;
}
for(int i=0;i<m;++i) if(!vis[i])
for(int j=i+1;j<m;++j) if(!vis[j])
E[i][j]--,E[j][i]--,vis[i]=vis[j]=1;
for(int i=0;i<m;++i) for(int j=0;j<m;++j) if(E[i][j]<0) return;
if(sol()) {
for(int i=0;i<ansE.size();++i) printf("%d %d\n",ansE[i].fir,ansE[i].sec);
for(int i=0;i<m;++i)
for(int j=0;j<i;++j)
if(E[i][j]<G[i][j]) printf("%d %d\n",node[i][0],node[j][0]);
for(int i=0;i<m;++i) for(int j=0;j<m;++j) pcnt[i][j]=pcnt[j][i]=0;
Flow::get(flow);
for(int i=0;i<m;++i)
for(int j=0;j<i;++j) if(E[i][j]) {
pcnt[i][j]=flow[id2[i]][id1[i][j]];
pcnt[j][i]=flow[id2[j]][id1[i][j]];
}
for(int i=0;i<m;++i)
for(int k=1;k<node[i].size();++k)
for(int j=0;j<m;++j) if(pcnt[i][j]) {
printf("%d %d\n",node[i][k],node[j][0]);
pcnt[i][j]--;
break;
}
exit(0);
}
}
void dfs(int cur) {
if(cur>=m-2) return getpru();
for(int i=0;i<m;++i) pru[cur]=i,dfs(cur+1);
}
int dig[200010];
char str1[20],str2[20];
int main() {
rd(n);
for(int i=1;i<=n;++i) dig[i]=dig[i/10]+1,node[dig[i]-1].PB(i);
m=dig[n];
int fail_flg=0;
for(int i=1;i<n;++i) {
scanf("%s%s",str1,str2);
int x=strlen(str1),y=strlen(str2);
x--,y--;
if(x==y) {
if(node[x].size()<=1) fail_flg=1;
int u=node[x].back(); node[x].pop_back();
int v=node[x].back();
ansE.PB(MP(u,v));
}
else G[x][y]++,G[y][x]++;
}
if(fail_flg) {
printf("-1");
return 0;
}
for(int i=0;i<m;++i) num[i]=node[i].size();
S=++ncnt,T=++ncnt;
for(int i=0;i<m;++i) for(int j=0;j<i;++j) id1[i][j]=++ncnt;
for(int i=0;i<m;++i) id2[i]=++ncnt;
dfs(0);
printf("-1");
return 0;
}
令 d i d_i di表示从第一个点出发到 i i i的最短路。不存在负环等价于我们能够给 d i d_i di赋一个实数值,使得对于任意一条边 e i , j e_{i,j} ei,j,我们都有 d j ≤ d i + e i , j d_j \le d_i + e_{i,j} dj≤di+ei,j。
不存在负环的时候,显然会有$d_i \le d_{i+1} 。 我 们 不 妨 考 虑 确 定 。我们不妨考虑确定 。我们不妨考虑确定q_i = d_{i+1}-d_i$的值。
考虑一条边 i → j ( i < j ) i\to j(i< j) i→j(i<j),它的限制相当于是 d j ≤ d i − 1 d_j \le d_i - 1 dj≤di−1,即 q j − 1 + q j − 2 + ⋯ q i ≥ 1 q_{j-1} +q_{j-2} + \cdots q_i \ge 1 qj−1+qj−2+⋯qi≥1。而对于一条边 j → i ( i < j ) j\to i(i< j) j→i(i<j),它的限制相当于是 d i ≤ d j + 1 d_i \le d_j+1 di≤dj+1,即 q j − 1 + q j − 2 + ⋯ q i ≤ 1 q_{j-1} +q_{j-2} + \cdots q_i \le 1 qj−1+qj−2+⋯qi≤1。发现我们实际上只需要考虑用 0 , 1 0,1 0,1去填这个 q i q_i qi数组,效果和用其他数字填是等价的。计算哪些边必须删去的时候,我们只需要关心为 1 1 1的数字的位置,可以考虑 d p dp dp,在状态中记录下最近的两个 1 1 1的位置,状态数 O ( n 2 ) O(n^2) O(n2),转移 O ( n ) O(n) O(n),总复杂度 O ( n 3 ) O(n^3) O(n3)。
#include
#include
#include
#include
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T> inline void cmin(T &x,T y) { x=min(x,y); }
const int N=510;
ll a[N][N],b[N][N];
ll f[N][N];
int n;
void getsum(ll a[][N]) {
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
ll query(ll a[][N],int x1,int x2,int y1,int y2) {
if(x2<x1||y2<y1) return 0;
x1--,y1--;
return a[x2][y2]-a[x1][y2]-a[x2][y1]+a[x1][y1];
}
int main() {
rd(n);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) {
if(i==j) continue;
int x; rd(x);
if(i<j) a[i][j]=x;
if(i>j) b[i][j]=x;
}
getsum(a),getsum(b);
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i)
f[0][i]=query(a,1,i-1,2,i);
for(int i=0;i<=n-1;++i)
for(int j=i+1;j<=n-1;++j)
for(int k=j+1;k<=n-1;++k)
f[j][k]=min(f[j][k],f[i][j]+query(a,j+1,k-1,j+2,k)+query(b,j+1,k,1,i));
ll ans=query(a,1,n-1,2,n);
for(int i=0;i<=n-1;++i)
for(int j=i+1;j<=n-1;++j)
ans=min(ans,f[i][j]+query(a,j+1,n-1,j+2,n)+query(b,j+1,n,1,i));
printf("%lld",ans);
return 0;
}
令a
为 1 1 1,b
为 2 2 2,则 s s s可以通过若干次操作后变成字符 c c c,当且仅当 s = c s=c s=c或者 s s s中包含两个相邻且相同的字符并且 s s s中所有字符的和模 3 3 3等于 c c c。
必要性显然。充分性可以考虑 s s s必然可以进行操作,并且操作完之后得到的串也一定可以进行操作,直到 ∣ s ∣ = 1 |s|=1 ∣s∣=1。
下面考虑如何判断能否通过对 s s s进行若干次操作得到 t t t:如果 s = t s=t s=t则可以;如果 ∣ s ∣ > ∣ t ∣ |s|>|t| ∣s∣>∣t∣且 s s s中相邻的字符都不相同显然不可以;将 s s s尽可能短的、且能够得到 t t t的第一个字符的前缀删去,然后对 s s s剩下的部分考虑 t t t的剩下的字符,最后匹配完 t t t的所有字符之后, s s s刚好用完或者剩下一个所有字符的和模 3 3 3为 0 0 0的后缀,则可以。
必要性证明:如果中间有一段划分包含了字符和为 0 0 0的后缀且除了这个后缀之外还包含相邻且相同的字符,则可以把这个后缀分到下一段去,所以让划分点尽量靠前一定不会更劣。
充分性证明:最后剩下的一段可以放到划分的最后一段去;如果最后一段恰只有一个字符并且和后面字符和为 0 0 0的段拼成了ababa
,那么我们可以把abab
分到这一段前面那段去,变成...abab|a
,直到abab
被分到的那段有相邻且相同的字符。
然后利用以上性质进行 d p dp dp即可,复杂度 O ( ∣ S ∣ ) O(|S|) O(∣S∣)。
#include
#include
#include
#include
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1e5+10,mod=1e9+7;
inline void Add(int &x,int y) { x+=y; if(x>=mod) x-=mod; }
inline void Dec(int &x,int y) { x-=y; if(x<0) x+=mod; }
char S[N];
int s[N],f[N],sum[N],n;
int lb[N],rb[N];
int main() {
scanf("%s",S+1); n=strlen(S+1);
int flg=0;
for(int i=1;i<n;++i) flg|=S[i]==S[i+1];
if(!flg) {
printf("1\n");
return 0;
}
for(int i=1;i<=n;++i) s[i]=s[i-1]+(S[i]-'a'+1);
int last[3]={0,-1,-1};
int lst=-1;
for(int i=1;i<=n;++i) {
if(i>1&&S[i-1]==S[i]) lst=i-2;
rb[i]=lst;
lb[i]=last[s[i]%3];
last[s[i]%3]=i;
}
f[0]=sum[0]=1;
for(int i=1;i<=n;++i) {
Add(f[i],f[i-1]);
if(lb[i]<rb[i]) {
Add(f[i],sum[rb[i]]);
if(lb[i]>-1) Dec(f[i],sum[lb[i]]);
}
sum[i]=(sum[i-1]+f[i])%mod;
}
int ans=0;
for(int i=1;i<=n;++i) if((s[n]-s[i])%3==0) Add(ans,f[i]);
printf("%d",ans);
return 0;
}
s[i]%3]=i;
}
f[0]=sum[0]=1;
for(int i=1;i<=n;++i) {
Add(f[i],f[i-1]);
if(lb[i]<rb[i]) {
Add(f[i],sum[rb[i]]);
if(lb[i]>-1) Dec(f[i],sum[lb[i]]);
}
sum[i]=(sum[i-1]+f[i])%mod;
}
int ans=0;
for(int i=1;i<=n;++i) if((s[n]-s[i])%3==0) Add(ans,f[i]);
printf("%d",ans);
return 0;
}