根据难度排序。
个人总结向。
如果有什么讲的不清楚的欢迎留言私信交流~

D. Duration(签到)

题意: 给出两个时分秒表示的时间,问相差多少秒。

思路:
化成秒相减就可以了。

代码:

#include<bits/stdc++.h>
#define pb push_back
#define ld long double
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define endl "\n"
#define pi acos(-1.0)
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

const int manx=1e6+5;
const int N=500+5;
const int mod=1e9+7;
const int mo=1e8;

ll h1,h2,m1,m2,s1,s2;
int main(){
    string a,b; 
    cin>>a>>b;
    ll cnt=0;
    for(int i=0;i<a.size();i++){
        if(a[i]==':') cnt++;
        if(cnt==0)h1=h1*10+(a[i]-'0');
        else if(cnt==1) m1=m1*10+(a[i]-'0');
        else s1=s1*10+(a[i]-'0');
    }
    cnt=0;
    for(int i=0;i<b.size();i++){
        if(b[i]==':') cnt++;
        if(cnt==0)h2=h2*10+(b[i]-'0');
        else if(cnt==1) m2=m2*10+(b[i]-'0');
        else s2=s2*10+(b[i]-'0');
    }
    s1=s1+m1*60+h1*3600;
    s2=s2+m2*60+h2*3600;
    cout<<abs(s1-s2)<<endl;
    return 0;
}

F. Fake Maxpooling(单调队列)

题意: n∗m的矩阵中每一位为gcd(i,j),求所有k∗k的子矩阵中最大值的和。

思路:
用单调队列 / 滑动窗口 维护 二维的窗口最大值。
其实和一维单调队列差不多,先对原矩阵 a 每一行求出长度为 k 的(横向)窗口的最大值,得到 ma 矩阵。
然后第二次枚举的元素不再是原本的矩阵,而是 ma 矩阵。
对 ma 矩阵每一列求出长度为 k 的(竖向)窗口的最大值,覆盖在原 ma 矩阵的位置,这里选择覆盖是因为这题对内存要求较高。
特别注意的点是求 ,可以记忆化一下,否则有超时的可能。
还有切记数组开 int 就够了,统计最大值之和开 ll 即可。

代码:

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

const int manx=5001;
const int N=1e3+5;
const int mod=1e9+7;

int a[manx][manx],ma[manx][manx],q[manx];
ll ans,head,tail,n,m,k;

int main()
{
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(!a[i][j])
                a[i][j]=a[j][i]=i/__gcd(i,j)*j;
    for(int i=1;i<=n;i++){
        head=1,tail=0;
        for(int j=1;j<=m;j++){
            while(head<=tail&&q[head]<(j-k+1)) ++head;
            while(head<=tail&&a[i][q[tail]]<=a[i][j]) --tail;
            q[++tail]=j;
            if(j>=k) ma[i][j]=a[i][q[head]];
        }
    }
    for(int i=k;i<=m;i++){
        head=1,tail=0;
        for(int j=1;j<=n;j++){
            while(head<=tail&&q[head]<(j-k+1)) ++head;
            while(head<=tail&&ma[q[tail]][i]<=ma[j][i]) --tail;
            q[++tail]=j;
            if(i>=k) ma[j][i]=ma[q[head]][i];
        }
    }
    for(int i=k;i<=n;i++)
        for(int j=k;j<=m;j++)
            ans+=ma[i][j];
    printf("%lld",ans);
    return 0;
}

C. Cover the Tree(dfs序+思维)

题意: 选最少数量的链来覆盖整颗树,使树的每条边都至少被覆盖一次。

思路:
比赛的时候是标记路径,贪心的选择叶子,也就是乱搞
正解是 dfs 序, 然后一对一对的输出叶子,注意叶子数目为奇数时,需将剩下的叶子与根节点连接为一条路径。

代码:

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

const int manx=1e6+5;
const int N=1e3+5;
const int mod=1e9+7;

vector<ll>g[manx];
ll ans[manx];
ll n,m,k;

void dfs(ll u,ll pre){
    if(g[u].size()==1) ans[++ans[0]]=u;
    for(auto v:g[u]){
        if(v==pre) continue;
        dfs(v,u);
    }
}
int main()
{
    io;
    cin>>n;
    for(int i=1;i<n;i++){
        ll u,v; cin>>u>>v;
        g[u].pb(v); g[v].pb(u);
    }
    dfs(1,-1);
    m=(ans[0]+1)/2;
    cout<<m<<endl;
    for(int i=1;i*2<=ans[0];i++)
        cout<<ans[i]<<" "<<ans[i+m]<<endl;
    if(ans[0]&1)  cout<<ans[m]<<" "<<1<<endl;
    return 0;
}

B. Boundary(计算几何)

题意: 在平面上给若干个点,求一个过原点的圆,使得尽量多的点在圆上。

思路:
三点确定一个圆,由于过原点,所以只需要 枚举即可,用 map 存半径进行统计。
精度的话 eps 设置 比较好。
最后输出时不要忘了加上原点。

代码:

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define eps 1e-10
using namespace std;

const int manx=1e6+5;
const int N=5e3+5;
const int mod=1e9+7;

struct Point{
    double x,y;
    Point(double x,double y){
        this->x=x;
        this->y=y;
    }
    Point(){
        x=y=0;
    }
};
Point p[N];
Point xin(Point a,Point b,Point c) 
{
  double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1*a1 + b1*b1)/2;
  double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2*a2 + b2*b2)/2;
  double d = a1*b2 - a2*b1;
  return Point(a.x + (c1*b2 - c2*b1)/d, a.y + (a1*c2 -a2*c1)/d);
}
map<pair<double,double> ,ll>mps;
int main(){
    ll n; scanf("%lld",&n);
    ll k=0; Point zero(0,0);
    for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
    ll ans=0;
    for(int i=1;i<=n;i++){
        mps.clear();
        for(int j=i+1;j<=n;j++){
            if(fabs(p[i].x*p[j].y-p[i].y*p[j].x)>eps){
                Point r=xin(p[i],p[j],zero);
                ans=max(ans,++mps[mp(r.x,r.y)]);
            }
        }
    }
    printf("%lld\n",++ans);
    return 0;
}

J. Just Shuffle(置换群)

题意: 给一个长度为n的1−n的排列,开始为1,2,3,……,n,给出经过k次相同置换后得到的排列,问置换一次后的排列。

思路:

置换群的符合运算:
对于 ,两边乘一个 k 的逆元 inv,有 ,化简得到:

因为k为大质数,所以在置换时循环大小不会变化。
对于每一个环单独考虑,给定排列的环大小就是置换的循环大小,将 当做一次置换,那么只要找到 时的 x ,也就是 k 的逆元,做 x 次置换,就可以得到答案。
注意给出的是置换完的样子,而不是置换。

代码:

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define eps 1e-10
using namespace std;

const int manx=1e6+5;
const int N=5e3+5;
const int mod=1e9+7;

ll exgcd(ll a,ll b,ll &x,ll &y){
    if(b==0){ x=1,y=0; return a;}
    ll gcd=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return gcd;
}
ll inv(ll a,ll b,ll &x,ll &y){
    ll gcd=exgcd(a,b,x,y);
    if(gcd!=1) return -1;
    else return (x+b)%b;
}
ll a[manx],vis[manx],ans[manx];
vector<ll>g;
int main(){
    ll n,k; cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        if(vis[i]) continue;
        vis[i]=1; g.clear(); g.pb(i);
        ll x=a[i],y;
        while(x!=i) g.pb(x),vis[x]=1,x=a[x];
        if(g.size()==1){ans[i]=i; continue;}
        x=inv(k%g.size(),g.size(),x,y);
        for(int j=0;j<g.size();j++) ans[g[j]]=g[(j+x)%g.size()];
    }
    for(int i=1;i<=n;i++)  cout<<ans[i]<<" ";
    return 0;
}

A. All with Pairs(KMP+Hash)

题意: 给出若干个字符串,定义 为串 的前缀与串 的后缀的最大重合长度。

思路:
对每个字符串的后缀进行hash,用map计数。
然后枚举每个字符串,枚举的时候计算前缀hash值,map中相同hash值的个数就是与其相匹配的后缀个数。
特别注意的是,题目要求的是最大的重合长度,那么如何取消短的前缀造成的重复计算?
这个时候可以想到 数组, 即可回滚长度,也就是说明有 子串 与 子串 有相同的前缀,这个时候就用 ,用可回滚处的答案减去较长子串对答案的贡献。
最后还有一个坑点就是 数组是对于字符串来说整体向右移动了一位,所以部分地方枚举或者计算答案需要注意下标。

代码:

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define eps 1e-10
using namespace std;

const int manx=1e6+5;
const int N=5e3+5;
//const int mod=1e9+7;
const int mod=998244353;
const ull mo=233;

string s[manx];
ll nexts[manx],ans[manx];
map<ull,ll>cnt;

void dohash(string s){
    ll n=s.size(); ull sum=0,base=1;
    for(int i=n-1;i>=0;i--){
        sum+=(s[i]-'a'+1)*base;
        base*=mo; cnt[sum]++;
    }
}
void donext(string p){
    ll i=0,j=-1,m=p.size();
    nexts[i]=j;
    while(i<m){
        if(j==-1||p[i]==p[j]) nexts[++i]=++j;
        else j=nexts[j];
    }
}
int main(){
    ll n; cin>>n; ll x=0;
    for(int i=1;i<=n;i++) cin>>s[i],dohash(s[i]);
    for(int i=1;i<=n;i++){
        donext(s[i]);
        ull sum=0;
        for(int j=0;j<s[i].size();j++){
            sum=sum*mo+(s[i][j]-'a'+1);
            ans[j+1]=cnt[sum];
        }
        for(int j=1;j<=s[i].size();j++)
            if(nexts[j]>0) ans[nexts[j]]-=ans[j];
        for(int j=1;j<=s[i].size();j++)
            x+=ans[j]%mod*j%mod*j%mod,x%=mod;
    }
    cout<<x<<endl;
    return 0;
}