woj4793~4795
今天的难度还真csp啊
盘王节
题意明晰:对方有御符,兵符,我有兵符。
一旦我的某个兵符攻击力大于等于对方的某个符,可以打碎那个符。
攻击方式有2:
①:打碎对面所有御符,然后可以选择直接攻击对方或者攻击对方兵符(直接攻击获得我的能力值大小伤害)。
②:攻击兵符,获得我和对方兵符能力差的伤害,同时击碎兵符。
考场60分,因为没有考虑打碎御符后可以攻击对方兵符。
注意能力有负数。意味着有时候攻击对方兵符比直接攻击对方要赚的多一些。
分两种情况考虑。一种是击碎对方全部御符(当然打不完就只能continue),然后击碎对方的负数兵符(获得更大贡献),然后获得我的所有正数贡献。(当然我的负数不用最好(但是我的负数可以拿来打碎对方负数的兵符))
一种是直接击碎兵符。当然从对方最小的开始,然后该怎么算怎么算。
写的很混乱,,
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
int n,m1,m2;
struct node{
int key,cnt;
}a[1000003],b[1000003],c[1000003];
bool cm(node a,node b){
return a.key<b.key;
}
bool cmm(node a,node b){
return a.key>b.key;
}
//clock_t st,ed;
int ans;int res[1000003],resc[1000003];
int sum,sumabs,ssabs;
signed main(){
// freopen("panwang3.in","r",stdin);
// freopen("panwang.out","w",stdout);
//st=clock();
n=in;m1=in;m2=in;
for(register int i=1;i<=n;++i)a[i].key=in,a[i].cnt=in;//sumabs+=(a[i].key>0)?(a[i].key*a[i].cnt):0;
//cout<<"#";
for(register int i=1;i<=n;++i)if(a[i].key>0)sumabs+=a[i].key*a[i].cnt;ssabs=sumabs;
for(register int i=1;i<=m1;++i)b[i].key=in,b[i].cnt=in;
for(register int i=1;i<=m2;++i)c[i].key=in,c[i].cnt=in;
//cout<<"#";
sort(a+1,a+n+1,cm);sort(b+1,b+m1+1,cm);sort(c+1,c+m2+1,cm);//cout<<"#";
//cout<<"#";
for(register int i=1,j=1;i<=n&&j<=m1;){
//cout<<i<<" "<<j<<endl;
while(a[i].key<b[j].key&&i<=n)++i;
if(i>n)break;
//if(sumabs<=0)break;
int x=min(a[i].cnt,b[j].cnt);
//cout<<x<<" "<<e[i].key<<" "<<e[i].cnt<<" "<<d[j].key<<" "<<d[j].cnt<<endl;
a[i].cnt-=x;
b[j].cnt-=x;res[i]+=x;
if(a[i].key>0)sumabs-=x*a[i].key;
if(!b[j].cnt)++j;if(!a[i].cnt)++i;
}//没考虑对方攻击为负数情况。
//a[++n].key=0;a[n].cnt=0x3f3f3f3f;sort
if(!b[m1].cnt){
int gusum=0;
for(int i=n,j=1;i>=1&&j<=m2;){
if(a[i].key<=c[j].key)break;
if(c[j].key>=0)break;
int x=min(a[i].cnt,c[j].cnt);
a[i].cnt-=x;c[j].cnt-=x;resc[j]+=x;res[i]+=x;
sumabs-=x*a[i].key;gusum+=x*a[i].key-x*c[j].key;
if(!a[i].cnt)--i;if(!c[j].cnt)++j;
}
// for(int i=1;i<=m2;i++)if(c[i].key<0)gusum+=c[i].key*c[i].cnt;
ans=max(ans,sumabs+gusum);//cout<<"#";
}
for(register int i=1;i<=n;++i)a[i].cnt+=res[i];
for(int i=1;i<=m2;i++)c[i].cnt+=resc[i];
sort(a+1,a+n+1,cmm);
int i=1,j=1;int sumnow=0;
for(;i<=n&&j<=m2;){
if(a[i].key<=c[j].key)break;
int x=min(a[i].cnt,c[j].cnt);
sumnow+=x*(a[i].key-c[j].key);
a[i].cnt-=x;c[j].cnt-=x;//ssabs-=x*a[i].key;
if(!a[i].cnt)++i;if(!c[j].cnt)++j;
}
ans=max(ans,sumnow);
cout<<ans;
//ed=clock();
//cout<<" "<<ed-st;
return 0;
}
祝箸节
要求给边染色,黑白两色。一个合法的最小生成树必须包含两种颜色的边。求染色方案数,使得合法最小生成树权值为给定的一个值X。
首先要一个结论。
结论:任何一种合法的最小生成树,在形态上只与原图最小生成树有最多一条边的差距。
简单证明:首先,如果最小生成树合法,那不用修改。
反之,最小生成树不合法,说明该MST边色相同。不妨都设为黑色。
如果新加的非树边要合法,则必须为白边。否则没有贡献。
此时原树变成了一棵含有1条白边,n-2条黑边的树。
此时再添加任何一条白边,都无法进入树。因为树已经合法。证毕(证了个寂寞,,乱写一通)
根据上述结论,你可以先做一次最小生成树。如果当前权值已经大于X,自然答案为0。
反之,对于每条非树边,求出强制包含这条边的不严格次小生成树,这个可以直接树上倍增做。环上减去最大边就行。
对于次小生成树的权值,分两种情况讨论。
如果该次小生成树权值等于X,记为equ++。如果小于,记为les++。大于不管。
然后对于最初的sum,也要讨论。
如果最初的sum已经等于X,那新加的边也只能是equ。
根据刚刚结论,有n-1条最小生成树边同色,而剩下的边中,有equ条边,这些边不能全都和MST同色。
容斥一下,总方案减去非法方案(即equ+n-1条边都同色),得到
这个2是因为颜色可以同时取反。
如果sum小于X,那我们在取能使答案合法的equ的同时,那些les边都要和MST同色,否则答案不会取equ。
使用容斥,
然后做完了。记得memset之类的东西。
变量名小心,我用的结构体(有点恶心)
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
int n,m,T,X;
struct bcj{
int fa[100003];
void clear(){for(int i=1;i<=n;i++)fa[i]=i;}
int find(int x){if(x==fa[x])return x;return fa[x]=find(fa[x]);}
}bcj;
struct tree{
int dep[100003],fa[100003][22],maxx[100003][22];
int first[100003],nxt[200003],to[200003],w[200003],tot;
void add(int a,int b,int c){
nxt[++tot]=first[a];first[a]=tot;to[tot]=b;w[tot]=c;
nxt[++tot]=first[b];first[b]=tot;to[tot]=a;w[tot]=c;
}
void clear(){memset(first,0,sizeof(first));tot=0;}
void dfs(int u,int faa){
dep[u]=dep[faa]+1;
fa[u][0]=faa;for(int i=1;i<=19;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=1;i<=19;i++)maxx[u][i]=max(maxx[u][i-1],maxx[fa[u][i-1]][i-1]);
for(int i=first[u];i;i=nxt[i]){
int v=to[i];if(v==faa)continue;
maxx[v][0]=w[i];dfs(v,u);
}
}
int query(int a,int b){
if(dep[a]<dep[b])swap(a,b);
int gu=0;
for(int i=19;i>=0;i--){
if(dep[fa[a][i]]>=dep[b]){
gu=max(gu,maxx[a][i]);a=fa[a][i];
}
}if(a==b)return gu;
for(int i=19;i>=0;i--){
if(fa[a][i]!=fa[b][i]){
gu=max(gu,max(maxx[a][i],maxx[b][i]));
a=fa[a][i],b=fa[b][i];
}
}return max(gu,max(maxx[a][0],maxx[b][0]));
}
}tree;
struct node{
int u,v,w,flag;
void clear(){
flag=0;
}
}edge[200003];
bool cm(node a,node b){
return a.w<b.w;
}
int sum,cnt;
int les,equ;
const int mod=1e9+7;
int ksm(int a,int b){
int gu=1;
while(b){//cout<<b<<endl;
if(b&1)gu=gu*a%mod;a=a*a%mod;b>>=1;
}return gu;
}
void solve(){
//for(int i=1;i<=m;i++)cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].w<<endl;
sum=cnt=les=equ=0;
for(int i=1;i<=m;i++){//cout<<"#";
int x=bcj.find(edge[i].u),y=bcj.find(edge[i].v);//cout<<" "<<x<<" "<<y<<endl;
if(x!=y){
// cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].w<<endl;
++cnt;sum+=edge[i].w;bcj.fa[x]=y;edge[i].flag=1;
tree.add(edge[i].u,edge[i].v,edge[i].w);
if(cnt==n-1)break;
}
}
//cout<<"Sum "<<sum<<endl;
if(sum>X){cout<<"0"<<'\n';return;}
tree.dfs(1,0);
int now=sum;
for(int i=1;i<=m;i++){
if(!edge[i].flag){
now=sum+edge[i].w-tree.query(edge[i].u,edge[i].v);
if(now==X)equ++;if(now<X)les++;
}
}
int ans=0;
//cout<<les<<" "<<equ<<endl;
if(sum==X){
ans+=(ksm(2,m)-2*ksm(2,m-n+1-equ)%mod+mod)%mod;ans%=mod;
}else{
ans+=2*(ksm(2,m-n+1-les)-ksm(2,m-n+1-les-equ)+mod)%mod;ans%=mod;
}
cout<<ans<<'\n';
}
signed main(){
T=in;
while(T--){
n=in;m=in;X=in;
bcj.clear();
tree.clear();
for(int i=1;i<=m;i++){
edge[i].flag=0;
edge[i].u=in;edge[i].v=in;edge[i].w=in;
}sort(edge+1,edge+m+1,cm);
//for(int i=1;i<=m;i++)cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].w<<endl;
solve();
}
return 0;
}
耍望节
今日神题。但也没太神。
从问题入手,问第k大合法串,k还很大,联想上次dzy出的那个倍增题(当然这道题的在线做法是倍增),可以大致感受这是一个dp题。
很明显,我们要像trie树那样潇洒走size的话,我们要求得的是,后面的合法方案数。这类似于trie树上dp,求子树size的那种。
然而,因为问号的存在,我们只用长串走到第i位,是不能限定一个结点的。
所以我们用表示长串走到第i位,当前匹配了短串的第j位的,i+1~n位填出来合法的方案数。
所以这是一个后缀方案数。
首先考虑转移:考虑第i+1位填什么,如果是给定数字只能填给定数字。但如果是问号就什么都可以填。
问题来了,填了这个数后,你和短串的匹配状态会变化。打断了匹配,并不一定是从头开始,你可能和前面的几个字母构成了短串的一个前缀。
这个前后缀的问题,,kmp自动机伺候一下。令表示短串第i位,和短串匹配到第j位,应该跳到的最长前后缀的那个地方,跟next一样,该怎么求怎么求,只是注意超出边界的fail赋成边界。这方便dp。看代码。
所以我们得到,大写I+1表示i+1位能填的数字。
边界f[n][m]=1;这个时候已经填完了,方案为1。
然后就可以反着dp了。
官方题解是在线倍增求第k大。
我是离线的。
如果我真在线做,复杂度nq,爆炸。
但是,我们发现,使得状态变多的罪魁祸首是问号。而一个问号往往意味着方案*10级别的增长。
去掉最后18个问号,因为m的长度是20,所以一共有38个问号可以影响答案。超过38个,自然对我们答案不能构成影响。
所以我们可以离线问题一遍dfs。
modify20191105 20:30 :巨神hzy和dhy提供了一组hack数据把本做法hack了,,我要去看题解了qwq!不过dhy提供了一种优化方法目前不知可不可行,正在(漫漫无期地)尝试中。
hack方法:在大串开头加一堆问号,然后GG。
(如果是随机数据,每个点只有十一分之一的几率出问号,如果前面出一堆问号的话,,是很可能卡掉我的做法的。不过为什么没卡掉还run得那么快是个问题,值得研究一下,有利于下次比赛乱搞随机化踩标算更好的分析复杂度)
//guess1:从前向后dp。
// FAIL:未知是否成功,无法正向搜索得到答案,中间状态无成功匹配的意义。
//guess2:从后向前dp。
// idea1:f[i][j]表示长串1~i都填好,短串弄到第j位的,后面合法方案数。因为是后面合法,所以
// 枚举下一位填的是什么(只能是对应数字,或者下一位为?)。
// 如果下一位为?,应当什么都可以填。故枚举下一位填甚。反之只有一个选择。
// f[i][j]=sum(f[i+1][fail[j][x]]); 使用kmp自动机求得fail(选择下一个可能打破选择) 。
// prob:方案数过大,不得取模。
//*idea*:发现k最大1e18。远小于最大总方案数1e(5e5)。故与1e18取min,如果等于inf,表示超出考虑范围。
// prob: 询问过多,如何处理。
//*idea*: 离线dfs。从小到大。反正对于每个询问,最多产生不到20次搜索分支。
// finish?
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}return cnt*f;
}
const int mod=1e9+7,inf=1e18+7;
int f[50003][22];
char ch[50003];int a[50003],b[22];
int nxt[22],fail[22][12];
int T,n,q,lena,lenb;
struct node{
int k,id;
}que[100003];
bool cm(node a,node b){
return a.k<b.k;
}
void add(int &a,int b){
a=(a+b);a=min(a,inf);
}
void getfail(){
memset(nxt,0,sizeof(nxt));memset(fail,0,sizeof(fail));
for(int i=2;i<=lenb;i++){
int j=nxt[i-1];while(j&&b[j+1]!=b[i])j=nxt[j];
nxt[i]=(b[j+1]==b[i])?(j+1):0;
}
for(int i=0;i<=lenb;i++){
for(int j=0;j<10;j++){
if(i==lenb)fail[i][j]=lenb;
else{
int k=i;while(k&&b[k+1]!=j)k=nxt[k];
fail[i][j]=(b[k+1]==j)?(k+1):0;
}
}
}
}int now;int ans[100003],pos;
void getans(int apos,int bpos,int Rank,int key){
if(pos>now)return;
if(apos>n){
while(que[pos].k==Rank+1&&pos<=now)ans[que[pos++].id]=key;return;
}
for(int i=0;i<10;i++){
if(a[apos]==i||a[apos]==233){
if(f[apos][fail[bpos][i]]+Rank>=que[pos].k)getans(apos+1,fail[bpos][i],Rank,((key*10)%mod+i)%mod);
if(pos>now||f[apos][fail[bpos][i]]==inf)return;
Rank+=f[apos][fail[bpos][i]];
}
}
}
signed main(){
// freopen("shuawang.in","r",stdin);
// freopen("shuawang.out","w",stdout);
T=in;
while(T--){
n=in;now=q=in;scanf("%s",ch+1);lenb=strlen(ch+1);
for(int i=1;i<=lenb;i++)b[i]=ch[i]-'0';pos=1;
scanf("%s",ch+1);//=strlen(a+1);
for(int i=1;i<=n;i++){
if(ch[i]=='?')a[i]=233;else a[i]=ch[i]-'0';
}
// for(int i=1;i<=n;i++)cout<<a[i]<<" ";cout<<endl;
// for(int i=1;i<=lenb;i++)cout<<b[i];cout<<endl;
getfail();
// for(int i=1;i<=lenb;i++)cout<<nxt[i]<<" ";cout<<endl;
memset(f,0,sizeof(f));f[n][lenb]=1;
for(int i=n-1;i>=0;i--){
for(int j=0;j<=lenb;j++){
for(int k=0;k<10;k++){
if(a[i+1]==k||a[i+1]==233){
add(f[i][j],f[i+1][fail[j][k]]);
}
}
}
}
// for(int i=0;i<=n;i++){
// for(int j=0;j<=lenb;j++)cout<<f[i][j]<<" ";cout<<endl;
// }
// for(int i=1;i<=lenb;i++){
// for(int j=0;j<10;j++)cout<<fail[i][j]<<" ";cout<<endl;
// }
for(int i=1;i<=q;i++){
que[i].k=in;que[i].id=i;
}sort(que+1,que+q+1,cm);
while(now&&que[now].k>f[0][0]){
ans[que[now--].id]=-1;
}getans(1,0,0,0);
for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
}
return 0;
}