1. 程式人生 > >暑假爆零歡樂賽SRM08題解

暑假爆零歡樂賽SRM08題解

發現 -s mod 統計 ide ons char 前綴和 第一個

  這真的是披著CF外衣的OI賽制?我怎麽覺得這是披著部分分外衣的CF?果然每逢cf賽制必掉rating,還是得%%%cyc橙名爺++rp。。

  A題就是找一找序列裏有沒有兩個連在一起的0或1,並且不能向兩端延伸(比如……1001……或110……或者……100),找到了之後就可以把整個序列分成這兩個數左邊,這兩個數和他的右邊三部分,然後這兩個0或1每個都能與左邊右邊串在一起構成兩個相同的子序列,並且這個子序列在原序列中只會出現這兩次,滿足題目條件。如果沒找到,再看看原來的序列裏是不是只有兩個0或1,那麽這樣單獨一個0或1也只在序列裏出現兩次。要是上邊兩種情況都不滿足,那麽隨便想想知道不可能存在只出現兩次的子序列。(cyc寫n>10輸出Y也過了,,,n很大的時候N的概率確實很小)

技術分享
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
char s[5010];
int main()
{
    int n,i,sum=0;
    scanf("%s",s); n=strlen(s);
    for(i=0;i<n;i++)
        if(s[i]==0)sum++;
    int flag=0;
    if(sum==2||n-sum==2
||(s[0]==s[1]&&s[1]!=s[2]))flag=1; for(i=3;i<=n;i++) if(s[i-3]!=s[i-2]&&s[i-2]==s[i-1]&&s[i-1]!=s[i])flag=1; if(flag)printf("Y");else printf("N"); }
A

  B的話可以寫bit優化dp(似乎也可以splay優化?不過我不會。。。)。用f[i][j]表示b序列跑到第i個數,a序列跑到第j個數的方案數,於是方程就是f[i][j]=sum(f[i-1][k])(1<=k<j&&b[i]+a[j]>=b[i-1]+a[k]),然後這個式子可以轉化成a[k]<=b[i]-b[i-1]+a[j],把a排個序之後發現這個k的取值是連續的,就能愉快地bit單點修改+前綴和查詢了。

技術分享
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
const int mod=1000000007;
int n,m;
struct data{
    int x,id;
}a[2010];
int b[1010],rank[2010],c[2010],f[1010][2010];
bool cmp(data a,data b){return a.x<b.x;}
int low(int x){return x&(-x);}
void add(int x,int k){for(;x<=n;x+=low(x))c[x]=(c[x]+k)%mod;}
int work(int x){int sum=0;for(;x;x-=low(x))sum=(sum+c[x])%mod;return sum;}
int main()
{
    int i,j;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&a[i].x),a[i].id=i;
    for(i=1;i<=m;i++)scanf("%d",&b[i]);
    sort(a+1,a+n+1,cmp);
    for(i=1;i<=n;i++)rank[a[i].id]=i;
    for(i=1;i<=n;i++)f[1][i]=1;
    for(i=2;i<=m;i++){
        for(j=1;j<=n;j++)c[j]=0;
        for(j=1;j<=n;j++){
            int l=0,r=n+1;
            while(l+1<r){
                int mid=(l+r)>>1;
                if(a[mid].x>b[i]-b[i-1]+a[rank[j]].x)r=mid;else l=mid;
            }
            f[i][j]=work(l); add(rank[j],f[i-1][j]);
        }
    }
    int ans=0;
    for(i=1;i<=n;i++)ans=(ans+f[m][i])%mod;
    printf("%d",ans);
}
B

  C的話,,,cyc的和正解的解法沒看懂。不過看了tjm的代碼,,,納尼?隨機化?把點隨機對半分然後跑最短路,然後重復幾次取最小值?不過這樣如果rp不好沒膜大佬還是會掛。。。那麽有沒有不用隨機化的方法呢?我們發現把點集對半分後,沒有跑過最短路的點對的兩個點都是在這兩個點集的某一個中,於是我們把兩個點集每個再分成兩半,第一個分成AB兩部分,第二個分成CD兩部分,然後把AC和BD重組成兩個點集再跑一次。這樣分分分分下去,分log(k)次,每個點對的距離肯定會在某一次跑最短路中被統計進答案。不過這樣實現還是有點麻煩,直接把k個特殊點標號,然後按照每一二進制位的值分集合,實現就簡便多了。

技術分享
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
int fir[100010],to[600010],w[600010],ne[600010];
int dist[100010],q[5000010],inq[100010];
int a[10010];
int n,m,k,tot=0;
void add(int x,int y,int z){to[++tot]=y; w[tot]=z; ne[tot]=fir[x]; fir[x]=tot;}
int spfa(int p)
{
    int i,h=1,t=0;
    for(i=1;i<=n;i++)dist[i]=1<<30,inq[i]=0;
    for(i=1;i<=k;i++)
        if(i&(1<<p))q[++t]=a[i],dist[a[i]]=0,inq[a[i]]=1;
    while(h<=t){
        for(i=fir[q[h]];i;i=ne[i])
            if(dist[q[h]]+w[i]<dist[to[i]]){
                dist[to[i]]=dist[q[h]]+w[i];
                if(!inq[to[i]]){
                    q[++t]=to[i]; inq[to[i]]=1;
                }
            }
        inq[q[h++]]=0; 
    }
    int ans=1<<30;
    for(i=1;i<=k;i++)
        if(!(i&(1<<p)))ans=min(ans,dist[a[i]]);
    return ans;
}
int main()
{
    int i,x,y,z;
    scanf("%d%d%d",&n,&m,&k);
    for(i=1;i<=k;i++)scanf("%d",&a[i]);
    for(i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z);
    }
    int ans=1<<30;
    for(i=0;i<=log(k);i++)ans=min(ans,spfa(i));
    printf("%d",ans);
}
C

暑假爆零歡樂賽SRM08題解