版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题解
第一次在考场上A掉状压DP。。。
由于答案要求路径条数为偶数的方案数,所以我们对于每个点只需要存下它对应路径条数的奇偶性
然后只有10个点,就可以状压DP
f[i][S]表示到第i层DAG时每个点的路径条数奇偶性为S
然后我们可以枚举当前层到上一层的边状态,直接转移到f[i-1][nS]
O(m*2^k*k^2)
我们可以预处理一下每一个邻接矩阵的行和列的二进制值
O(m*2^k*k)
就可以1e8复杂度卡过了
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 10005
const int mod=998244353;
int f[N][1035],a[N][12][12],b[12],c[12],hang[N][12],lie[N][12];
int main()
{
//freopen("1.in","r",stdin);
freopen("adore.in","r",stdin);
freopen("adore.out","w",stdout);
int n,m,i,j,k;
n=gi();m=gi();n-=2;
for(i=0;i<m;i++)
b[i]=gi();;
for(i=2;i<=n;i++)
for(j=0;j<m;j++)
for(k=0;k<m;k++){
a[i][j][k]=gi();
hang[i][j]+=(a[i][j][k]<<k);
lie[i][k]+=(a[i][j][k]<<j);
}
int s=0,st=(1<<m),ns1,ns2,cnt;
for(i=0;i<m;i++){
c[i]=gi();
s+=(c[i]<<i);
}
f[n][s]=1;
for(i=n;i>1;i--){
for(s=0;s<st;s++){
if(f[i][s]){
ns1=0;ns2=0;
for(j=0;j<m;j++)
if((s>>j)&1){
ns1^=lie[i][j];
ns2^=hang[i][j];
}
f[i-1][ns1]+=f[i][s];
if(f[i-1][ns1]>=mod)f[i-1][ns1]-=mod;
f[i-1][ns2]+=f[i][s];
if(f[i-1][ns2]>=mod)f[i-1][ns2]-=mod;
}
}
}
int ans=0;
for(s=0;s<st;s++){
if(f[1][s]){
cnt=0;
for(j=0;j<m;j++)
if(s&(b[j]<<j))
cnt++;
if(!(cnt&1)){
ans+=f[1][s];
if(ans>=mod)ans-=mod;
}
}
}
printf("%d",ans);
}
题解
有一个利用bitset的O(n^3/64)的显然做法(其实预处理已经n^2了,为什么不T呢……)
然后我们可以证明随机猜中答案的期望次数是3n
所以我们只要知道一个解的时候就可以return 0了
结果考试的时候pw只开了10,然后爆零了。。。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
using namespace std;
#define ULL unsigned long long
#define N 6005
char s[2005];
int pw[100];//XXXXXXXXXXXXXXXXXXXXXXXXX
bitset<12015>a[N];
int main()
{
freopen("confess.in","r",stdin);
freopen("confess.out","w",stdout);
int n,i,j,len=0,tmp;
scanf("%d",&n);n++;
pw[1]=0;pw[2]=1;pw[4]=2;pw[8]=3;pw[16]=4;pw[32]=5;pw[64]=6;
for(i=1;i<=n;i++){
scanf("%s",s+1);
if(!len){
len=strlen(s+1);
tmp=6*len-2*n;
tmp=(1<<6)-(1<<tmp);
}
s[len]=char(((s[len]-33)&tmp)+33);
int k=0;
for(j=1;j<=len;j++){
int x=s[j]-33;
while(x){
a[i].set(k+pw[x&-x]);
x-=x&-x;
}
k+=6;
}
}
for(i=1;i<n;i++){
for(j=i+1;j<=n;j++){
if(int((a[i]&a[j]).count())>=(n>>1|1)){
printf("%d %d",i,j);
return 0;
}
}
}
printf("NO Solution");
}
题解
明显贪心
然后开始用左偏树乱做。。。WA爆了。。。
想一想,其实这道题并不难
贪心的一个思路就是在恰好满足自身条件的情况下,尽可能大的减少其他地方的代价
也就是将自己子树中的距离恰好为k的点覆盖完后,尽可能地去覆盖其他较深的点
于是我们设f[i][k]表示i的子树中与i距离为k的未覆盖的点的个数
g[i][k]表示i的子树中与i距离为k的可用的灭火器的剩余灭火次数
由于K<=20,我们就可以直接枚举k的大小,用g[i][k]来覆盖f[i][K-k]和f[i][K-k-1](因为K-k-1再向上走就无法与g[i][k]距离<=k了)
最后要在1号点把所有的未覆盖点再像之前一样覆盖一遍,统计最后的未覆盖点数,再确定1号店放多少灭火器
就做完啦
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 300005
int n,s,k;
int fir[N],to[N],nxt[N],fa[N],cnt;
void adde(int a,int b)
{
to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;
}
int f[N][25],g[N][25],ans;
void dfs(int u)
{
int v,p,i,tmp;f[u][0]=1;
for(p=fir[u];p;p=nxt[p]){
v=to[p];
if(v!=fa[u]){
fa[v]=u;dfs(v);
for(i=1;i<=k;i++){
f[u][i]+=f[v][i-1];
g[u][i]+=g[v][i-1];
g[u][i]=min(g[u][i],n);
}
}
}
if(f[u][k]){
ans+=int(1.0*f[u][k]/s+0.9999999);
g[u][0]+=min(n,int(1.0*f[u][k]/s+0.9999999)*s);
}
for(i=0;i<=k;i++){
tmp=min(f[u][i],g[u][k-i]);
f[u][i]-=tmp;g[u][k-i]-=tmp;
}
for(i=0;i<k;i++){
tmp=min(f[u][i],g[u][k-i-1]);
f[u][i]-=tmp;g[u][k-i-1]-=tmp;
}
}
int main()
{
freopen("repulsed.in","r",stdin);
freopen("repulsed.out","w",stdout);
int i,j,u,v,tmp;
n=gi();s=gi();k=gi();
for(i=1;i<n;i++){
u=gi();v=gi();
adde(u,v);
}
dfs(1);
for(i=0;i<=k;i++)
for(j=k;j>=0;j--){
if(i+j<=k){
tmp=min(f[1][i],g[1][j]);
f[1][i]-=tmp;g[1][j]-=tmp;
}
}
int sum=0;
for(i=0;i<=k;i++)
sum+=f[1][i];
printf("%d",ans+int(1.0*sum/s+0.9999999));
}