2017 SiChuan collegiate programming contest 训练总结 【持续更新中】

路还很长啊~

A Simple Arithmetic


水题, 但是我傻逼了, 把大于long long 值赋给 ll变量了 , 这题唯一注意的就是 -9223372036854775808 -1, 正数ll 最大值比负数绝对值小1

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
int main()
{
    ll a, b;
    while(~scanf("%lld%lld", &a, &b))
    {
        if(a == ((ll)1<<63)  && b == -1)
        {
            puts("9223372036854775808");
        }
        else if((a < 0 && b > 0 || a > 0 && b < 0) && a%b)
        {
            ll ans = a/b;
            printf("%lld\n", ans-1);
        }
        else
        {
            ll ans = a/b;
            printf("%lld\n", ans);
        }
    }
    return 0;
}

B Broken Counter

————————————————————————————————————————————————


C Determinant

————————————————————————————————————————————————


D Dynamic Graph

———————————————————————————————————————————————

给你一个DAG图,每个节点不是白的就是黑的,有q次操作,每次将点x变换颜色.然后输出当前有多少对白点

方法1:转自tabris

考虑到DAG图,所以进行拓扑排序, 
每次操作完,之后将黑色的点去掉,然后进行拓扑序,在过程中记录能到达每个点的个数,然后一路算过去就行了,然后去个重复采用vis标记,复杂度是 O(Tqnm)  ,然后妥妥的TLE了,

最后想到用二进制来优化,每一位对应表示每一个节点,然后进行TOP过程中一下就行了.

可以用5个64位整形计算 (这时候注意求1的个数的时候要用lowbit优化) 
也可以用bitset来计算

最后复杂度为 O(Tqnm/a)  ,其中 a 为bitset优化的常数,因为|操作如果用数组实现, 是要for一边,on, 每条边都要这样,但是bitset会有优化~

这种算法的具体解释是, bit[x]里的每个1代表,有哪些白点能到达x这个白点, 这样拓扑遍历, 能到达他的祖先的点, 肯定也能到达他的子节点, 所以就是bit[to] = bit[to] | bit[u], 最后for一边记录所有能到达这个点的点有几个,就是有几对

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <bitset>
#include <queue>
using namespace std;
const int maxn = 305 + 5;
int n, m, q, x, y, deg[maxn], temp[maxn], book[maxn], col[maxn];
vector<int> v[maxn];
void TOP()
{
    bitset<maxn> bit[maxn];
    int ans = 0;
    queue<int> q;
    for(int i = 1; i <= n; i++)
    {
        if(!deg[i])
            q.push(i);
        temp[i] = deg[i];
    }
    for(int i = 1; i <= n; i++)
        bit[i][i] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i = 0; i < v[u].size(); i++)
        {
            int to = v[u][i];
            if(!col[u] && !col[to])
                bit[to] = bit[to] | bit[u];
            temp[to]--;
            if(!temp[to]) q.push(to);
        }
    }
    for(int i = 1; i <= n; i++)
        ans += bit[i].count()-1;
    printf("%d\n", ans);
}
int main()
{
    while(~scanf("%d%d%d", &n, &m, &q))
    {
        memset(col, 0, sizeof(col));
        memset(deg, 0, sizeof(deg));
        for(int i = 1; i <= n; i++)
            v[i].clear();
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d", &x, &y);
            v[x].push_back(y);
            deg[y]++;
        }
        while(q--)
        {
            scanf("%d", &x);
            col[x] = !col[x];
            TOP();
        }
    }
    return 0;
}
方法2,转自牛逼队友shilongbao(话说我们三个彼此菜逼/牛逼称号换的有点频繁啊

这个题也可以有两种做法,首先我们最基本的暴力去做肯定超时,复杂度O(n*m*q),

官方题解给出的做法就是,我们可以记录f[x][y]为x到y的路径条数,然后维护这个路径.

当要将v变为黑点时 f[x][y]-=f[x][v]*f[v][y],反之变白就是+

  最后O(n2)枚举任意两点之间的路径条数,>0就是一对点.


总结下, 这里将两个白点转换成能否到达, 最开始n3, 枚举中间点,找出所有两个点之间的路径数, 有黑点,就代表

以他为某个中间点的所有路都没了~这种思维方式还需要加强,以后遇到图论删点,加点都可以想想这种方法吧


#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const int maxn = 350;
LL dp[maxn][maxn], vis[maxn];
int N, M, Q;
void init()
{
    for(int i = 1; i <= N; i++)
    {
        vis[i] = 0;
        for(int j = 1; j <= N; j++)
            dp[i][j] = 0;
    }
}
int main ()
{
    while(~scanf("%d %d %d", &N, &M, &Q))
    {
        init();
        for(int i = 1; i <= M; i++)
        {
            int u, v;
            scanf("%d %d", &u, &v);
            dp[u][v]++;
        }
        for(int k = 1; k <= N; k++ )
            for(int st = 1; st <= N; st++)
                for(int ed =  1; ed <= N; ed++)
                    dp[st][ed] += dp[st][k] * dp[k][ed];
        for(int i = 1; i <= Q; i++)
        {
            int u;
            scanf("%d", &u);
            if(vis[u] == 0)
            {
                vis[u] = 1;
                for(int st = 1; st <=N; st++)
                    for(int ed = 1; ed <= N; ed++)
                        dp[st][ed] -= dp[st][u] * dp[u][ed];
            }
            else
            {
                vis[u] = 0;
                for(int st = 1; st <=N; st++)
                    for(int ed = 1; ed <= N; ed++)
                        dp[st][ed] += dp[st][u] * dp[u][ed];
            }
            int ans = 0;
            for(int st = 1; st <= N; st++)
            {
                if(vis[st]) continue;
                for(int ed = 1; ed <= N; ed++)
                {
                    if(vis[ed]) continue;
                    if(dp[st][ed] > 0) ans++;
                }
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}


E Longest Increasing Subsequence

——————————————————————————————————————————————

好题!

题目大意:


给你N个数组成的序列,现在规定F【i】表示的是以a【i】结尾的最长上升子序列长度。

LIS(1)表示的是删除第一个数之后,F【i】的亦或和。

现在然你求LIS(1~N);


思路:

题意有点绕, 删除第i个数, 对i前面的数没有影响, 对后面的数影响最多就是-1,那么什么时候-1呢?

转自mengxiang000

然后分两种情况考虑:


①当F【j】依旧为F【j】的时候,那么说明位子j前边存在某些位子使得F【x】=F【j】-1&&a【x】<a【j】&&x!=i(删除的位子);

②当F【j】为F【j】-1的时候,就说明位子j前边没有存在那样的位子。


所以我们过程维护一个数组 s【Z】表示F值为Z的位子上最小的a【】;


就能贪心的使得s【Z】尽可能的小,那么满足F【j】依旧为F【j】的情况就越来越多了。

简而言之, 就是看长度为j前面F【j】-1的最小末尾数字是多少,如果大于等于a[j],说明删掉那个是唯一一个长度为F【j】-1,并且小于a【j】的,

所以要维护每个长度的最小值。

本来只会n2log算法, nlog的时候,不要删除的那个数字,却没有观察题目...删除一个,最多就影响后面的, 当只有他这一个选择的时候才会影响。。这里存下长度的末尾最小值比较屌。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 5e3 + 5;
const int maxn = 5e3 + 5;
typedef long long ll;
int dp[maxn], minx[maxn], a[maxn];
int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= n; i++)
        {
            dp[i] = 1;
            for(int j = 1; j < i; j++)
                if(a[j] < a[i])
                    dp[i] = max(dp[i], dp[j]+1);
        }
        for(int k = 1; k <= n; k++)
        {
            for(int i = 1; i < maxn; i++) minx[i] = INF;
            ll ans = 0;
            for(int i = 1; i <= n; i++)
            {
                if(i == k) continue;
                int temp = dp[i];
                if(i > k && a[i] <= minx[dp[i]-1]) temp = dp[i]-1;
                ans ^= (temp*temp);
                minx[temp] = min(minx[temp], a[i]);
            }
            printf("%lld%c", ans, k == n ? '\n' : ' ');
        }
    }
    return 0;
}

F Simple Algebra

———————————————————————————————————————————————— 
给你 f(x,y)=ax2+bxy+cy2 ,问你是否永远非负

题意就是f(x,y) >= 0 恒成立, 把左右同时除以y2, 换元, 就是一个一元二次方程了

分类讨论a,b,c是否为0的情况

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;

int main()
{
    ll a, b, c, d;
    while(cin >> a >> b >> c)
    {
        if(a < 0) puts("No");
        else if(a == 0)
        {
            if(b == 0)
            {
                if(c >= 0) puts("Yes");
                else puts("No");
            }
            else puts("No");
        }
        else
        {
            if(b*b-4*a*c <= 0) puts("Yes");
            else puts("No");

        }
    }
    return 0;
}

G 2017

H Roads

————————————————————————————————————————————————

I Strange Prime

————————————————————————————————————————————————

J Skewness

————————————————————————————————————————————————

给你一个n*n的方阵 求每个子矩阵的元素和的三次方的和

思路:三次方很暴力很麻烦,掌握一次方的求法就好了

for(int i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++)
        a[i][j] = a[i][j] * i * (n-i+1) * j * (n-j+1); //上下左右的情况,+1,是表示可以枚举的格子

K 2017 Revenge

———————————————————————————————————————————————


L Nice Trick

———————————————————————————————————————————————— 
给你一个序列以及 

s3=ii<j<knaiajak=(ni=1ai)33(ni=1a2i)(ni=1ai)+2(ni=1a3i)6

问你 ii<j<k<lnaiajakal

水题......这么水的题....思路被带歪了, 他告诉你on求三个数相乘, 4个数相乘, 就是枚举第四个数,

乘上前i个数三个相乘的情况,也就是上面的公式- -

利用公式的代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int Mod = 1e9 + 7;
const int maxn = 1e5 + 5;
typedef long long ll;
ll a[maxn], inv6;
ll quick_mul(ll x, ll n)
{
    ll ans = 1;
    while(n)
    {
        if(n&1) ans = (ans*x)%Mod;
        x = (x*x)%Mod;
        n /= 2;
    }
    return ans;
}
ll cal(ll a1, ll a2, ll a3)
{
    ll ans = 0;
    ans += quick_mul(a1,3);
    ans -= a1*a2%Mod*3%Mod;
    ans = (ans % Mod + Mod ) % Mod;
    ans += a3*2%Mod;
    return ans*inv6%Mod;
}
int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        for(int i = 1; i <= n; i++)
            scanf("%lld", &a[i]);
        ll ans = 0;
        ll sum1 = (a[1] + a[2] + a[3])%Mod;
        ll sum2 = ((a[1]*a[1])%Mod + (a[2]*a[2])%Mod + (a[3]*a[3])%Mod)%Mod;
        ll sum3 = ((quick_mul(a[1], 3) + quick_mul(a[2],3))%Mod+quick_mul(a[3],3))%Mod;
        inv6 = quick_mul((ll)6, Mod-2);
        for(int i = 4; i <= n; i++)
        {
            ans = (ans+(cal(sum1, sum2, sum3)*a[i])%Mod)%Mod;
            sum1 = (sum1 + a[i])%Mod;
            sum2 = (sum2 + (a[i]*a[i])%Mod)%Mod;
            sum3 = (sum3 + quick_mul(a[i],(ll)3))%Mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

这题dp也很简单啊, dp[i][j]代表前i个数选j个相乘, 方程就是

dp[i][j] = (dp[i-1][j]+a[i]*dp[i-1][j-1])%mod; 第i个选还是不选,很常规的二维dp....

dp代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int mod = 1e9+7;
ll a[maxn], dp[maxn][5];

int main(void)
{
    int n;
    while(cin >> n)
    {
        for(int i = 1; i <= n; i++)
            scanf("%lld", &a[i]);
        memset(dp, 0, sizeof(dp));
        dp[1][1] = a[1];
        for(int i = 2; i <= n; i++)
        {
            for(int j = 1; j <= 4; j++)
            {
                if(j == 1) dp[i][j] = (dp[i-1][j]+a[i])%mod;
                else dp[i][j] = (dp[i-1][j]+a[i]*dp[i-1][j-1])%mod;
            }
        }
        printf("%lld\n", dp[n][4]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值