2017 计蒜之道 复赛 <状压DP+最短路>

B. Windows 画图

在 Windows 的“画图”工具里,可以绘制各种各样的图案。可以把画图当做一个标准的二维平面,在其上先后绘制了 nn 条颜色互不相同的线段。

按绘制的时间顺序,从先到后把线段依次编号为 11 到 nn。第 ii 条线段的两个端点分别为 (xa_i,ya_i)(xai,yai) 和 (xb_i,yb_i)(xbi,ybi),线段的粗细忽略不计。后绘制的线段不会改变之前绘制的线段的位置。

请写一个程序,回答 qq 组询问,每组询问给出一个坐标 (x_i,y_i)(xi,yi),你需要算出在这个点上最后绘制的线段编号。

输入格式

第一行包含两个正整数 n,m(1\leq n\leq 80000,1\leq m\leq 250)n,m(1n80000,1m250),分别表示线段的数目以及坐标的最大取值(下面会具体说明)。

接下来 nn 行,每行输入四个正整数 xa_i,ya_i,xb_i,yb_ixai,yai,xbi,ybi (1\leq xa_i,ya_i,xb_i,yb_i\leq m,(1xai,yai,xbi,ybim, (xa_i,ya_i)\neq(xb_i,yb_i))(xai,yai)(xbi,ybi)),依次表示每条线段两个端点的坐标。

接下来一行,输入一个正整数 q(1\leq q\leq 62500)q(1q62500),表示询问的组数。

接下来 qq 行,每行输入两个正整数 x_i,y_i(1\leq x_i,y_i\leq m)xi,yi(1xi,yim),分别表示每组询问的坐标。

输出格式

输出 qq 行,每行一个整数,表示该位置最上面(最后绘制)的线段的编号。

若该点上不存在线段,请输出 00

样例解释

样例对应题目描述中的图。

样例输入
5 8
2 5 5 2
5 2 3 8
8 4 1 4
2 2 5 8
8 7 4 1
4
3 4
5 2
6 4
3 5
样例输出
4
2
5
0

求出两个坐标关于x,y差值的最大公约,然后差值各/最大公约,得到的2个数每+一次,就是从一个点到另一个点移动的过程,而且这个过程必定是在格子上的。

#include <iostream>
#include <stdio.h>
#include <queue>
#include <string.h>
#include <stdlib.h>
#include <set>
#include <algorithm>
#include <map>
using namespace std;
int qq[305000];
int gcd(int x,int y){
    if(x%y==0)
        return y;
    else
        return gcd(y,x%y);
}
int walk[300][300];
int main(){
	int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4;
    int r,c,n,m;
    //freopen("in.txt","r",stdin);
    int g1,g2,g3;
    cin >>n>>m;
    memset(walk,0,sizeof(walk));
    for(i=1;i<=n;i++){
        scanf("%d %d %d %d",&t1,&t2,&t3,&t4);
        if(t1==t3){
            for(j=min(t2,t4);j<=max(t2,t4);j++)
                walk[t1][j]=i;continue;
            }else if(t2==t4){
            for(j=min(t1,t3);j<=max(t1,t3);j++)
                walk[i][t2]=i;continue;
            }
        f1=t3-t1;f2=t4-t2;
        int t5=0;
        if(abs(f2)>abs(f1)){
            swap(f1,f2);
            t5=1;
        }
        f3=gcd(abs(f1),abs(f2));
       if(t5)swap(f1,f2);
        g1=f1/f3;g2=f2/f3;
        while(t1!=t3||t2!=t4){
            walk[t1][t2]=i;
            t1+=g1;t2+=g2;
        }
        walk[t3][t4]=i;
    }
    cin >> r;
    for(i=1;i<=r;i++){
        scanf("%d %d",&t1,&t2);
        printf("%d\n",walk[t1][t2]);
    }
    return 0;
}

百度地图上有 nn 个城市,城市编号依次为 11 到 nn。地图中有若干个城市群,编号依次为 11 到 mm。每个城市群包含一个或多个城市;每个城市可能属于多个城市群,也可能不属于任何城市群。

地图中有两类道路。第一类道路是 城市之间的快速路,两个城市 u,vu,v 之间增加一条距离为 cc的边;第二类道路是 城市群之间的高速路,连接两个城市群 a,ba,b,通过这条高速路,城市群 aa 里的每个城市与城市群 bb 里的每个城市之间两两增加一条距离为 cc 的边。图中所有边均为无向边。

你需要计算从城市 ss 到城市 tt 的最短路。

输入格式

第一行输入 n(1 \le n \le 20000),n(1n20000), m(0 \le m \le 20000)m(0m20000),分别表示城市总数和城市群总数。

接下来一共输入 mm 行。

第 ii 行首先输入一个 k_i(1 \le k_i \le n)ki(1kin),表示第 ii 个城市群中的城市数为 k_iki。接下来输入 k_iki个数,表示第 ii 个城市群中每个城市的编号(保证一个城市群内的城市编号不重复且合法,\sum_{i=1}^{m}k_i \le 20000i=1mki20000)。

下一行输入一个整数 m_1(0 \le m_1 \le 20000)m1(0m120000),表示有 m_1m1 条第一类道路,即 城市之间的快速路

接下来 m_1m1 行,每行输入三个整数 u_i,v_i(1 \le u_i, v_i \le n),c_i(1 \le c_i \le 10^6)ui,vi(1ui,vin),ci(1ci106),分别表示快速路连接的两个城市编号和边的距离。

下一行输入一个整数 m_2(0 \le m_2 \le 20000)m2(0m220000),表示有 m_2m2 条第二类道路,即 城市群之间的高速路

接下来 m_2m2 行,每行输入三个整数 a_i,b_i(1 \le a_i, b_i \le m),l_i(1 \le l_i \le 10^6)ai,bi(1ai,bim),li(1li106),分别表示快速路连接的两个城市群编号和边的距离。

最后一行输入 s, t(1 \le s, t \le n)s,t(1s,tn),表示起点和终点城市编号。

输出格式

输出一个整数,表示城市 ss 到城市 tt 到最短路。如果不存在路径,则输出-1

样例说明

1 -> 2 - > 5或者1 -> 4 -> 5是最短的路径,总长度为 1212

样例输入
5 4
2 5 1
2 2 4
1 3
2 3 4
2
1 2 9
1 5 18
2
1 2 6
1 3 10
1 5
样例输出
12

问最短路,刚开始觉得6W这会超,然后想4w也就是只建立一个点当作城市群看待,可是这样发现这样会导致城市群之间的距离为0了,所以必须得多建立4w个点,当作从一个群的城市到这个群对应的入点的距离为0,这个城市群与各个出城市群是有有向边的,表明这两个城市群可相连,如果1 2 可连,那么需要从如城市群1连到出城市群2,再从如城市群2连到出城市群1,连2次,然后spfa跑一次单源最短路即可。

刚开始一直连样例都看不懂,原来是可以通过城市群从a到b,再从b到a。

#include <iostream>
#include <stdio.h>
#include <queue>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <set>
#include <algorithm>
#include <map>
using namespace std;
typedef long long ll;
struct ttt{
int next;
ll cost;
};
vector<ttt>qq[60500];
ll dist[60500];
int main(){
	int i,j,k,f1,f2,f3,f4,t1,t2,t4;
	ll t3;
    int r,c,n,m;
    //freopen("in.txt","r",stdin);
    int g1,g2,g3;
    cin >>n>>m;
    for(i=1;i<=n+m+m;i++)
        dist[i]=1e14+7;
    ttt u,v;
    u.cost=0;
    for(i=1;i<=m;i++){
            scanf("%d",&t1); //i为第i个城市群
        for(j=1;j<=t1;j++){
            scanf("%d",&t2);
            u.next=n+i;
            //cout <<t2 << "连接" << u.next <<endl;
            qq[t2].push_back(u);
            u.next=t2;
            //cout <<i+m+n << "连接" << u.next <<endl;
            qq[i+n+m].push_back(u);
        }
    }
    scanf("%d",&k);
    for(i=1;i<=k;i++){
            scanf("%d %d %lld",&t1,&t2,&t3);
            u.cost=t3;
            u.next=t2;
            qq[t1].push_back(u);
            u.next=t1;
            qq[t2].push_back(u);}
    scanf("%d",&k);
    for(i=1;i<=k;i++){
        scanf("%d %d %lld",&t1,&t2,&t3);
            u.cost=t3;
            u.next=n+t2+m;
            //cout << n+t1 << "连接~~~" <<u.next <<endl;
            qq[n+t1].push_back(u); //左边向右边,单向
            u.next=n+t1+m;
            //cout << m+t2 << "连接~~~!!" <<u.next <<endl;
            qq[n+t2].push_back(u);
    }
    int s,e;
    cin >> s >>e;
    dist[s]=0;
    queue<int>q1;
    q1.push(s);
    int u1;
    while(!q1.empty()){
        u1=q1.front();q1.pop();
        for(i=0;i<qq[u1].size();i++){
                u=qq[u1][i];
      //  cout <<u1<<"到" << u.next << endl;
            if(dist[u.next]>dist[u1]+u.cost){
               dist[u.next]=dist[u1]+u.cost;
               q1.push(u.next);
            }
        }
    }
    if(dist[e]<1e14+7)
    cout <<dist[e] <<endl;
    else
    cout <<"-1" <<endl;
    return 0;
}

腾讯推出了一款益智类游戏——消消乐。游戏一开始,给定一个长度为 nn 的序列,其中第 ii个数为 A_iAi

游戏的目标是把这些数全都删去,每次删除的操作为:选取一段连续的区间,不妨记为 [L,R][L,R],如果这一段区间内所有数的最大公约数 \geq kkkk 值在游戏的一开始会给定),那么这一段区间就能被直接删去。

注意:一次删除以后,剩下的数会合并成为一个连续区间。

定义 f(i)f(i) 为进行 ii 次操作将整个序列删完的方案数。

你需要实现一个程序,计算 \sum_{i=1}^{n}{(f(i) \ast i)} \text{ mod } 1000000007i=1n(f(i)i) mod 1000000007

输入格式

第一行输入两个整数 n,k(1\le n \le 18)n,k(1n18)

第二行输入 nn 个正整数 a_i(1 \le a_i \le 10^5)ai(1ai105),表示初始序列中的每个数。

输入数据保证 1 \le k \le \min(a_1,a_2,\ldots a_n)1kmin(a1,a2,an)

输出格式

输出一个整数,表示算出的答案。

样例说明

对于样例 1 而言,f(1)=1f(1)=1f(2)=9f(2)=9f(3)=26f(3)=26f(4)=24f(4)=24

对于样例 2,f(1)=0f(1)=0f(2)=2f(2)=2

样例输入1
4 1
1 1 1 1
样例输出1
193
样例输入2
2 2
2 3
样例输出2
4
样例输入3
1 233
233
样例输出3
1
状态压缩DP,不会。看的题解~

做不出原因:~ 太菜,dp知识不到位

~题目的结果要求是dp[4][1]*1+dp[4][2]*2+dp[4][3]*3这样的形式,就想办法凑成这样的形式

dp[i][j]表示到第i个集合第k步有多少种方法。

显然可以得到当S可以一步去完结的时候,

dp[S|i][k]+=dp[i][k-1]

意思为从i这个集合+一个集合的集合可以由i这个集合在k-1步数只需要多加一步,就可以全部变为S|i集合。

之所以这个步数的转移可以在内部用for,是因为这个状态是由dp[i][k]转移来的,而枚举状态是通过i枚举,那么i的所有可能性都在后面不会改变了的

至于为什么这么循环S|i这个集合中的i都是已经计算好的,这点不是很明白。

这里学到了一个关于如何求集合S以外全部集合的方法。

具体做法:

for(i=0;i<(1<<n);i++){
        for(u=0;u<n;u++){
            if(i>>u&1)continue;
            S=0;
            nowgcd=q1[u];
            for(v=u;v<n;v++){ 
                if(i>>v&1)continue;
                S|=1<<v;  



#include <iostream>
#include <stdio.h>
#include <queue>
#include <string.h>
#include <stdlib.h>
#include <set>
#include <algorithm>
#include <map>
using namespace std;
typedef long long ll;
const ll INF=1e9+7;
ll dp[(1<<18)+5][20];
ll q1[20];
ll gcd1(ll x,ll y){
    if(x%y==0)
        return y;
    else return gcd1(y,x%y);
}
ll gcd(ll x,ll y){
    if(y>x)
        swap(x,y);
    return gcd1(x,y);
}
int main(){
	ll  i,j,k,f1,f2,f3,f4,t1,t2,t3,t4;
    ll  r,c,n,m;
    ll  l1,l2,l3;
    //freopen("in.txt","r",stdin);
    cin >> n>> m;
    for(i=0;i<n;i++){ //状压,二进制之类从0开始写
        cin >> q1[i];
        //dp[(1<<i)][1]=1;
    }
     dp[0][0]=1;
    ll u,v,S,nowgcd;
    t1=0;
    for(i=0;i<(1<<n);i++){
        for(u=0;u<n;u++){
            if(i>>u&1)continue;
            S=0;
            nowgcd=q1[u];
            for(v=u;v<n;v++){ //这两个for的意思是,从u为起点,找到全部无i的集合
                if(i>>v&1)continue;
                nowgcd=gcd(nowgcd,q1[v]); //这里nowgcd是公约以后的结果
                if(nowgcd<m)break;
                S|=1<<v;  //把所有满足的v全部放进来
                for(k=1;k<=n;k++){//因为每次都是从i开始向上加,那么就不用把k的循环放到最外层
                        //这样子是可以很大程度增加速度
                    dp[S|i][k]+=dp[i][k-1];
                    dp[S|i][k]%=INF;
                /*if((S|i)==3){
                cout <<(S|i) << "由" <<S<<"  ++++   "<<i <<"组成" <<endl;
                cout << dp[S|i][k]  << "~ " <<k <<"层"<<endl;
                cout << dp[i][k-1] <<"      " <<i <<"   "<<k-1<<endl;
                }*/
                    //这里是S|i的这个集合,S是可以一次性拿掉的,
                    //那么S|i的拿走的次数就为dp[i][k-1]步加上拿走S这一步组成
            // 因为i是枚举的,那么必定不会漏/重复
            }
            }
        }
    }
    ll sum1=0;
    for(i=1;i<=n;i++){
        sum1+=dp[(1<<n)-1][i]*i;
        sum1%=INF;
    }
    cout << sum1<<endl;
    return 0;
}








  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值