先放题解吧,考的很悲桑
第一题组合计数类DP
缩点之后易得对于每个固定形态的树有ans=(s1^d1*s2^d2……)
然后我们知道把一个树的普吕弗序列乘起来得s1^(d1-1)*s2^(d2-1)……
然后ans=普吕弗序列乘积*(s1*s2……)
设普吕弗序列长度为L(题解中的n-2并不标准)
因为sigma(s)=n
所以ans=simga(普吕弗序列乘积)*(s1*s2……)
ans=n^L*(s1*s2……)
我们只需要求出来sigma((s1*s2……)),dp就可以了
设f[i][j]表示i个元素分成j个联通块,我们考虑1的连通块大小则有
f[i][j]=simga(f[i-k][j-1]*C[i-1][k-1]*(m-1)!/2*m))
其中(m-1)!/2是环内方案数,m是环长
最后注意不存在二元环以及整个是一个环的情况
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long LL;
const int maxn=210;
int n,mod;
int C[maxn][maxn];
int sum[maxn];
int f[maxn][maxn];
void Get_C(){
C[0][0]=1;
for(int i=1;i<=n;++i){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;++j){
C[i][j]=C[i-1][j-1]+C[i-1][j];
if(C[i][j]>=mod)C[i][j]-=mod;
}
}return;
}
void Get_DP(){
sum[1]=1;sum[3]=3;
for(int i=4;i<=n;++i)sum[i]=sum[i-1]*i%mod;
f[0][0]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=i;++j){
for(int k=1;k<=i;++k){
f[i][j]=f[i][j]+1LL*f[i-k][j-1]*C[i-1][k-1]*sum[k]%mod;
if(f[i][j]>=mod)f[i][j]-=mod;
}
}
}
int ans=1;
for(int i=3;i<=n-1;++i)ans=ans*i%mod;
int now=1;
for(int i=2;i<=n;++i){
ans=ans+1LL*f[n][i]*now%mod;
if(ans>=mod)ans-=mod;
now=now*n%mod;
}printf("%d
",ans);return;
}
int main(){
freopen("land.in","r",stdin);
freopen("land.out","w",stdout);
scanf("%d%d",&n,&mod);
Get_C();Get_DP();
return 0;
}
至于第二题嘛,首先这是一道论文题,而且还是论文为了引入数位DP算法用的题目
所以这是一道很简单的题目
首先设n的长度为len,长度不为len的和很好计算,随便搞一搞就好了
之后我们考虑n的限制,分奇数偶数讨论
类似数位DP算贡献
如果len是偶数,那么每一位的符号是确定的,只需要知道出现的次数就可以了,扫一遍即可
如果len是奇数,相邻两个数互相抵消,只会剩下1,然后算有多少对就可以了
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
LL n;
int Num[22],L;
LL Solve(int n,int k){
if(k&1){
LL d=-1;
for(int i=1;i<=n;++i)d=d*10;
return d/2;
}else{
if(!(n&1))return 0;
LL d=-45;
for(int i=1;i<n;++i)d=d*10;
return d;
}
}
LL Get_num(LL pre,int n){
int len=0,tmp=1;
LL p=pre,sum=0;
while(p){
sum=sum+(p%10)*tmp;
tmp=-tmp;len++;p/=10;
}sum=sum*(-tmp);
for(int i=1;i<=n;++i)sum=sum*10;
LL ans=Solve(n,n+len);
if(!((n+len)&1))ans+=sum;
return ans;
}
LL Get_ans(LL n){
if(n<10){
LL ans=0;
for(int i=1;i<=n;++i){
if(i&1)ans+=i;
else ans-=i;
}return ans;
}
memset(Num,0,sizeof(Num));L=0;
LL u=n;
while(u)Num[++L]=u%10,u/=10;
LL ans=5;
for(int i=1;i<L;++i){
for(int j=1;j<=9;++j){
ans-=Get_num(j,i-1);
}
}
LL pre=0;
for(int i=L;i>=2;--i){
int end=Num[i];
for(int j=0;j<end;++j){
if(pre)ans-=Get_num(pre,i-1);
pre++;
}pre*=10;
}
int tmp=Num[1];
int t=-1;
for(int i=0;i<=tmp;++i){
memset(Num,0,sizeof(Num));L=0;
LL now=pre+i;
while(now){
Num[++L]=now%10;
now/=10;
}
for(int j=L;j>=1;--j){
ans+=Num[j]*t;
t=-t;
}
}return ans;
}
int main(){
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
while(scanf("%lld",&n)==1){
if(!n)break;
printf("%lld
",Get_ans(n));
}return 0;
}
第三题是个提交答案题,自己失误很大
首先第一个点爆搜写错了,第三个点没开long long,第十个点标程写错了
QAQ 一共炸掉了30分,真是悲桑
但是为什么失误这么大呢,显然是因为自己给题答只留了一个小时多一点的时间,其他时间用来check第二题和磕第一题了
如果按照CTSC我给题答留个两个多小时的话,应该能搞出50-60分
而且吐槽一句:这个题答太不标准了,没checker,没评分参数,不然我怎么会挂掉?
挂掉还是自己弱
第一个点爆搜就可以了,自己犯蠢挂了
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=200010;
int n,m;
int ans=0;
int val[maxn];
struct OP{
char type;
int u,v,val;
}c[maxn];
int Get_ans(int S){
int ans=0;
for(int i=0;i<n;++i)if(S>>i&1)ans+=val[i];
for(int i=1;i<=m;++i){
int u=c[i].u,v=c[i].v;
if(c[i].type=='A'){
if(S>>u&1){
if(S>>v&1)ans+=c[i].val;
}
}else if(c[i].type=='B'){
if((S>>u&1)||(S>>v&1))ans+=c[i].val;
}else if(c[i].type=='C'){
if(S>>u&1){
if(!(S>>v&1))ans+=c[i].val;
}
}else if(c[i].type=='D'){
if(S>>v&1){
if(!(S>>u&1))ans+=c[i].val;
}
}else{
if(!(S>>u&1)){
if(!(S>>v&1))ans+=c[i].val;
}
}
}return ans;
}
int main(){
freopen("shell1.in","r",stdin);
freopen("shell1.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)scanf("%d",&val[i]);
for(int i=1;i<=m;++i){
c[i].type=getchar();
while(c[i].type<'!')c[i].type=getchar();
scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val);
c[i].u--;c[i].v--;
}
for(int i=0;i<(1<<n);++i){
ans=max(ans,Get_ans(i));
}printf("%d
",ans);
return 0;
}
第二个点注意到所有的权都很大,所以只是让你去写高精度
你贪心的去取所有正权点,会发现所有正权边也会被取到
然后答案就显然了
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int maxn=52;
int n,m,u,v;
char s[maxn];
struct big_num{
int a[52],len;
void init(){
len=0;memset(a,0,sizeof(a));
len=strlen(s+1);
for(int i=len;i>=1;--i)a[len-i+1]=s[i]-'0';
}
void add(const big_num &A){
len=max(len,A.len)+1;
for(int i=1;i<=len;++i){
a[i]=a[i]+A.a[i];
if(a[i]>=10)a[i]-=10,a[i+1]++;
}
while(len>0&&a[len]==0)len--;
}
void print(){
for(int i=len;i>=1;--i)printf("%d",a[i]);
}
}ans,A;
int main(){
freopen("shell2.in","r",stdin);
freopen("shell2.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",s+1);
if(s[1]=='-')continue;
A.init();ans.add(A);
}
for(int i=1;i<=m;++i){
char ch=getchar();
while(ch<'!')ch=getchar();
scanf("%d%d",&u,&v);
scanf("%s",s+1);
if(s[1]=='-')continue;
A.init();ans.add(A);
}ans.print();
return 0;
}
第三个点你会发现边权都特别大,然后点权是-1
然后限制都是B,所以我们一定能取得所有的边,问题就转化成了用最少的点
限制是u,v两个之中至少有一个被选中,二分图匹配即可
注意开long long
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
const int maxn=200010;
int n,m,u,v,w;
LL ans=0;
int val[maxn];
int h[maxn],cnt=1;
int vis[maxn],tim;
int girl[maxn];
struct edge{
int to,next;
}G[3000010];
void add(int x,int y){
++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
bool find(int u){
for(int i=h[u];i;i=G[i].next){
int v=G[i].to;
if(vis[v]==tim)continue;
vis[v]=tim;
if(!girl[v]||find(girl[v])){
girl[v]=u;girl[u]=v;
return true;
}
}return false;
}
int main(){
freopen("shell3.in","r",stdin);
freopen("shell3.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&val[i]);
for(int i=1;i<=m;++i){
char ch=getchar();
while(ch<'!')ch=getchar();
scanf("%d%d%d",&u,&v,&w);
add(u,v);add(v,u);
ans+=w;
}
for(int i=1;i<=n;++i){
if(girl[i])continue;
tim++;
if(find(i))ans--;
}printf("%lld
",ans);
return 0;
}
第四个点只有C限制,而且边权都很小且是负数
我们就发现我们一条边也不能选,限制变成了选u就必须选v,求最大点权和
最大权闭合子图即可
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
using namespace std;
const int maxn=200010;
const int oo=0x7fffffff/3;
int n,m,S,T;
int u,v,w;
int ans=0;
int val[maxn];
int h[maxn],cnt=1;
int cur[maxn];
struct edge{
int to,next,w;
}G[3000010];
queue<int>Q;
int vis[maxn];
void add(int x,int y,int z){
cnt++;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt;
++cnt;G[cnt].to=x;G[cnt].next=h[y];G[cnt].w=0;h[y]=cnt;
}
bool BFS(){
for(int i=S;i<=T;++i)vis[i]=-1;
Q.push(S);vis[S]=1;
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=h[u];i;i=G[i].next){
int v=G[i].to;
if(G[i].w>0&&vis[v]==-1){
vis[v]=vis[u]+1;
Q.push(v);
}
}
}return vis[T]!=-1;
}
int DFS(int x,int f){
if(x==T||f==0)return f;
int w,used=0;
for(int i=cur[x];i;i=G[i].next){
if(vis[G[i].to]==vis[x]+1){
w=f-used;
w=DFS(G[i].to,min(G[i].w,w));
G[i].w-=w;G[i^1].w+=w;
if(G[i].w>0)cur[x]=i;
used+=w;if(used==f)return used;
}
}
if(!used)vis[x]=-1;
return used;
}
void dinic(){
while(BFS()){
for(int i=S;i<=T;++i)cur[i]=h[i];
ans-=DFS(S,oo);
}return;
}
int main(){
freopen("shell4.in","r",stdin);
freopen("shell4.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&val[i]);
for(int i=1;i<=m;++i){
char ch=getchar();
while(ch<'!')ch=getchar();
scanf("%d%d%d",&u,&v,&w);
add(u,v,oo);
}
S=0;T=n+1;
for(int i=1;i<=n;++i){
if(val[i]>0)ans+=val[i],add(S,i,val[i]);
else add(i,T,-val[i]);
}
dinic();
printf("%d
",ans);
return 0;
}
第五个点我们观察数据会发现有一个点点权很大,一个点点权很小,其他点都是0
之后看边权,发现边权和都是负的,证明负的点权一定不能取
且正点权一定能取,由于边权都是负数,所以能不选就不选
因为全是C操作
可以把正权点看成S,负权点看成T,从S到T求最小割即可
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
using namespace std;
const int maxn=200010;
const int oo=0x7fffffff;
int n,m,S,T;
int u,v,w;
int ans;
int val[maxn];
int h[maxn],cnt=1;
int cur[maxn];
struct edge{
int to,next,w;
}G[3000010];
queue<int>Q;
int vis[maxn];
void add(int x,int y,int z){
++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt;
++cnt;G[cnt].to=x;G[cnt].next=h[y];G[cnt].w=0;h[y]=cnt;
}
bool BFS(){
for(int i=1;i<=n;++i)vis[i]=-1;
Q.push(S);vis[S]=1;
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=h[u];i;i=G[i].next){
int v=G[i].to;
if(G[i].w>0&&vis[v]==-1){
vis[v]=vis[u]+1;
Q.push(v);
}
}
}return vis[T]!=-1;
}
int DFS(int x,int f){
if(x==T||f==0)return f;
int w,used=0;
for(int i=cur[x];i;i=G[i].next){
if(vis[G[i].to]==vis[x]+1){
w=f-used;
w=DFS(G[i].to,min(G[i].w,w));
G[i].w-=w;G[i^1].w+=w;
if(G[i].w>0)cur[x]=i;
used+=w;if(used==f)return used;
}
}
if(!used)vis[x]=-1;
return used;
}
void dinic(){
while(BFS()){
for(int i=1;i<=n;++i)cur[i]=h[i];
ans-=DFS(S,oo);
}return;
}
int main(){
freopen("shell5.in","r",stdin);
freopen("shell5.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&val[i]);
if(val[i]>0)ans+=val[i];
}
for(int i=1;i<=m;++i){
char ch=getchar();
while(ch<'!')ch=getchar();
scanf("%d%d%d",&u,&v,&w);
add(u,v,-w);
}
S=12345;T=98765;
dinic();
printf("%d
",ans);
return 0;
}
UPD:第九个点和第五个点一样,所以程序差不多
第六个点我们发现数据时完全随机无规律,所以只能用近似算法了
模拟退火乱搞即可,搞的并没有答案优QAQ
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define eps 1e-5
using namespace std;
const int maxn=200010;
const int oo=0x7fffffff;
int n,m,ans;
int val[maxn];
bool vis[maxn];
struct OP{
char type;
int u,v,val;
}c[maxn];
double R(){return rand()%10000/10000.0;}
int Get_ans(){
int sum=0;
for(int i=1;i<=n;++i)if(vis[i])sum+=val[i];
for(int i=1;i<=m;++i){
int u=c[i].u,v=c[i].v;
if(c[i].type=='A'){
if(vis[u]&&vis[v])sum+=c[i].val;
}else if(c[i].type=='B'){
if(vis[u]||vis[v])sum+=c[i].val;
}else if(c[i].type=='C'){
if(vis[u]&&!vis[v])sum+=c[i].val;
}else if(c[i].type=='D'){
if(!vis[u]&&vis[v])sum+=c[i].val;
}else if(!vis[u]&&!vis[v])sum+=c[i].val;
}return sum;
}
void SA(double T){
int tmp=0,cur=0;
for(int i=1;i<=n;++i)vis[i]=rand()%2;
cur=Get_ans();
while(T>eps){
int now=rand()%n+1;
vis[now]^=1;
tmp=Get_ans();
ans=max(ans,tmp);
int d=tmp-cur;
if(d>0||exp(d/T)>R())cur=tmp;
else vis[now]^=1;
T*=0.998;
}return;
}
int main(){
freopen("shell6.in","r",stdin);
freopen("shell6.out","w",stdout);
srand(5211314);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&val[i]);
for(int i=1;i<=m;++i){
c[i].type=getchar();
while(c[i].type<'!')c[i].type=getchar();
scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val);
}
int T=1000;ans=-oo;
while(T--)SA(10000);
printf("%d
",ans);
return 0;
}
第七个点和第八个点数据都做错了,不过我还是写了标程的
很容易发现他想要做的数据是分块的,然后分块模拟退火即可
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define eps 1e-5
using namespace std;
const int maxn=200010;
const int oo=0x7fffffff;
int n,m,ans,Ans;
int val[maxn];
bool vis[maxn];
struct OP{
char type;
int u,v,val;
}c[maxn];
double R(){return rand()%10000/10000.0;}
int Get_ans(){
int sum=0;
for(int i=1;i<=100;++i)if(vis[i])sum+=val[i];
for(int i=1;i<=m;++i){
int u=c[i].u,v=c[i].v;
if(c[i].type=='A'){
if(vis[u]&&vis[v])sum+=c[i].val;
}else if(c[i].type=='B'){
if(vis[u]||vis[v])sum+=c[i].val;
}else if(c[i].type=='C'){
if(vis[u]&&!vis[v])sum+=c[i].val;
}else if(c[i].type=='D'){
if(!vis[u]&&vis[v])sum+=c[i].val;
}else if(!vis[u]&&!vis[v])sum+=c[i].val;
}return sum;
}
void SA(double T){
int tmp=0,cur=0;
for(int i=1;i<=100;++i)vis[i]=rand()%2;
cur=Get_ans();
while(T>eps){
int now=rand()%100+1;
vis[now]^=1;
tmp=Get_ans();
ans=max(ans,tmp);
int d=tmp-cur;
if(d>0)cur=tmp;
else vis[now]^=1;
T*=0.9;
}return;
}
int main(){
freopen("shell7.in","r",stdin);
freopen("shell7.out","w",stdout);
srand(5211314);
scanf("%d%d",&n,&m);
m/=1000;
for(int i=1;i<=n;++i)scanf("%d",&val[i]);
for(int k=1;k<=1000;++k){
for(int i=1;i<=m;++i){
c[i].type=getchar();
while(c[i].type<'!')c[i].type=getchar();
scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val);
c[i].u=c[i].u%100+1;c[i].v=c[i].v%100+1;
}
ans=-oo;int T=300;
while(T--)SA(10000);
Ans+=ans;
for(int i=1;i+100<=n;++i)val[i]=val[i+100];
}
printf("%d
",Ans);
return 0;
}
第十个点m=n-1,观察边很容易发现这是棵树
设f[i][0/1]表示选不选,然后做一遍树形DP就可以了
讨论的时候注意细节,连std都讨论错了
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=200010;
int n,m;
int u,v,w;
int h[maxn],cnt=0;
int val[maxn];
LL f[maxn][2];
struct edge{
char type;
int to,next,w;
}G[maxn];
void add(int x,int y,int z,char c){
cnt++;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;G[cnt].type=c;h[x]=cnt;
}
void DP(int u){
f[u][0]=0;f[u][1]=val[u];
for(int i=h[u];i;i=G[i].next){
int v=G[i].to;
DP(v);
if(G[i].type=='A'){
f[u][0]=f[u][0]+max(f[v][0],f[v][1]);
f[u][1]=f[u][1]+max(f[v][0],f[v][1]+G[i].w);
}else if(G[i].type=='B'){
f[u][0]=f[u][0]+max(f[v][0],f[v][1]+G[i].w);
f[u][1]=f[u][1]+G[i].w+max(f[v][0],f[v][1]);
}else if(G[i].type=='C'){
f[u][0]=f[u][0]+max(f[v][0],f[v][1]);
f[u][1]=f[u][1]+max(f[v][0]+G[i].w,f[v][1]);
}else if(G[i].type=='D'){
f[u][0]=f[u][0]+max(f[v][0],f[v][1]+G[i].w);
f[u][1]=f[u][1]+max(f[v][0],f[v][1]);
}else{
f[u][0]=f[u][0]+max(f[v][0]+G[i].w,f[v][1]);
f[u][1]=f[u][1]+max(f[v][1],f[v][0]);
}
}return;
}
int main(){
freopen("shell10.in","r",stdin);
freopen("shell10.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&val[i]);
for(int i=1;i<=m;++i){
char ch=getchar();
while(ch<'!')ch=getchar();
scanf("%d%d%d",&u,&v,&w);
if(u>v){
swap(u,v);
if(ch=='B')ch='C';
else if(ch=='C')ch='B';
}
add(u,v,w,ch);
}
DP(1);
printf("%lld
",max(f[1][0],f[1][1]));
return 0;
}