开篇碎碎念
第一次组队完成的组队训练任务,感觉果然和个人赛不一样,唔有一种有人兜底的安全感bushi(虽然赛后还是需要补题//可恶)
赛时过了五题,然后补了两题))所以题解就写一下ABEFGIK好啦,嗯...其实是因为其他的题开不出来(小声...)
A.全1子矩阵
题意:
判断是不是这个n行m列的矩阵是不是存在x1,x2,y1,y2;使在这个范围内的都是1,其余地方都是0;
注意特判一下全0矩阵也是不合法的
解题思路:
从前往后遍历一下第一个出现1的行st,从后往前找一下最后一个出现1的行ed;
在st行里面找到第一个1出现的位置l,最后一个出现1的位置r
判断是否从st到ed的每一行l~r范围内都是1,范围外都是0
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=50;
const int mod=1e9+7;
const int inf=1e18;
int a[MAXN][MAXN];
void sol(int n,int m){
string s;
for(int i=1;i<=n;i++){
cin>>s;
for(int j=0;j<m;j++){
a[i][j+1]=s[j]-'0';
}
}
int ls=-1,rs=-1,st=-1;
for(int i=1;i<=n;i++){//找到开始行的左右两个端点
for(int j=1;j<=m;j++){
if(a[i][j]==1){
ls=j;
break;
}
}
if(ls>0){
for(int j=m;j>=1;j--){
if(a[i][j]==1){
rs=j;
break;
}
}
st=i;
break;
}
}
int ed=-1;
for(int i=n;i>=st;i--){//找结束行
for(int j=1;j<=m;j++){
if(a[i][j]==1){
ed=i;
break;
}
}
if(ed>0)break;
}
// cerr<<ls<<' '<<rs<<endl;
// cerr<<st<<' '<<ed<<endl;
int f=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]){
if(i<st || i>ed || j<ls || j>rs){
f=0;break;
}
}
else{
if((i>=st && i<=ed) && (j>=ls && j<=rs)){
f=0;
break;
}
}
}
if(f==0)break;
}
if(st==-1 || ed==-1 || ls==-1 || rs==-1){
cout<<"No"<<endl;
return;
}
if(!f){
cout<<"No"<<endl;
return ;
}
else cout<<"Yes"<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int x,y;
while(cin>>x>>y){
sol(x,y);
}
return 0;
}
B.组合数
题意:
给出n和k,输出Cnk和1e18的最小值
反思:
最一开始发现了前者是Cnk的组合数,于是想对于每个k,求出最小的满足乘积大于1e18的n;打了一个长长的表之后发小交上去WA了,突然意识到在紧贴1e18的地方,可能会存在在除分母之前的瞬间爆longlong,于是想用int128,但是int128变量类型的输入输出有些忘记...(一开始还以为是编译器坏掉了捏)
其实其实明明可以直接交python的,周天的牛客就有一道大数据的题目(虽然是猜结论的签到题)但是直接用python卡过去了(呜呜还不是因为用C++手写高精T了一直化简式子还是T),但是python的一行输入好几个数不太会,只依稀记得一个split,最后还是翻python自带的help才找到的。
赛时最后是用了一手long double去判断是不是大于int128,然后输出还是用的long long。
但是还是因为这个题牵扯了一下情绪(毕竟是签嘛长着一副签的样子)
思路:
就像在前面反思里提到的用了一下long double去判断
code:
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
const int MAXN=3e5+10;
const int mod=1e9+7;
#define inf 1000000000000000000
#define ld long double
#define int long long
int a[MAXN];
void sol(int n,int k){
ld ans=1;
int aa=1;
if(k>n/2){
k=n-k;
}
// cerr<<n<<' '<<k<<endl;
for(int i=1;i<=k;i++){
ans=ans*(n-i+1)/i;
aa=ans;
if(ans>=inf || ans<0){
cout<<inf<<endl;
return ;
}
}
cout<<aa<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
// cin>>t;
// t=1;
int n,k;
while(cin>>n>>k){
sol(n,k);
}
return 0;
}
E.Numbers
题意:
给定一个长度不超过50的字符串s,判断s是否能由有限个互不相同的0到99中的数拼接而成
思路:
爆搜 因为字符串的长度不超过50,而且不超过100组,也就是说总和不超过5000,配上5s的时间限制,长着一副可以爆搜的样子。
分别讨论一下当前位置是否可以留1位,2位,判断能否保留两位的话需要判断一下是否是结尾(这里RE了一阵子)
注意剪枝:当1位2位均不能保留的时候return;
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=3e5+10;
const int mod=1e9+7;
const int inf=1e18;
string s;
int vis[105],n,ans;
void dfs(int pos){
if(pos==n){
ans++;
return ;
}
int num=s[pos]-'0';
int f=0;
if(!vis[num]){//保留1位
vis[num]=1;
f=1;
dfs(pos+1);
vis[num]=0;
}
if(s[pos]!='0' && pos!=n-1){//保留两位
num=(s[pos]-'0')*10+s[pos+1]-'0';
if(vis[num] && !f){
return ;
}
if(!vis[num]){
vis[num]=1;
dfs(pos+2);
vis[num]=0;
}
}
}
void sol(){
for(int i=0;i<100;i++){
vis[i]=0;
}
n=s.size();
ans=0;
dfs(0);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
// cin>>t;
// t=1;
while(cin>>s){
sol();
}
return 0;
}
F.4 Bottons
题意:
有四种按钮,分别可以向右移动不超过a步,向上移动不超过b步,向左移动不超过c步,向下移动不超过d步。总共可以按下不超过n次,问总共能到达多少个不同的坐标格
思路:
一个没有严谨证明的第六感:因为可以移动的都是不超过某个上限值,所以选左就没必要选右,选上就没必要选下
所以就分象限讨论,对于左上来说可以分1~(n-1)步给左,其余部分给向上(也就是b(n-k),所以可以转化成n-1个底边为c,高度为b(n-k)的小矩形求和
然后再将横纵轴上的相加即可
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=3e5+10;
const int mod=1e9+7;
const int inf=1e18;
void sol(int n){
int a,b,c,d;
cin>>a>>b>>c>>d;
int ans=0;
ans=(a*b%mod+b*c%mod+c*d%mod+a*d%mod)%mod;
ans=ans*(n*(n-1)/2%mod)%mod;
int tot=0;
tot=(a+b+c+d)%mod;
ans=ans+(tot*n)%mod;
ans=(ans+1)%mod;
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
// cin>>t;
// t=1;
int n,k;
while(cin>>n){
sol(n);
}
return 0;
}
G.字典序
题意:
给定一个矩阵,调整一下矩阵中列的顺序(这个顺序需要字典序尽量小),使得调整后的矩阵从逐行字典序不降
思路:
参考了组酥雨大大的博客,呜呜呜他好强
从两行的矩阵开始看起,对于一部分位置C1i < C2i,一部分大于,还有部分相等。影响字典序的是前两部分。题目要求的是前面的行字典序小,所以每个第二部分的前面都必须要至少有一个属于前者的。
那么也就是说要想使用后面的需要解锁一下限制...
分析一下限制,假如对于第三列来说,C13和C23都大于C33,那么也就是第三列必须解锁两次限制(即一定放了至少一个比第三行处在该位置的小的列)把前两行都排好之后才有可能排第三列;同样相邻的两行间可能有多对关系属于逆序的。所以是一个多对多的关系,考虑拓扑排序
所以分别记录了一下使用了第K列之后可以调整哪几行,以及每一行可以开哪些列的限制,每一列都还有几道限制未开。
由于题目要保证字典序最小,所以每次都要选择列号最小的,没有限制未开的列
code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int a[MAXN][MAXN];
int lim[MAXN];//每一列的目前的限制数————拓扑排序中的出/入边数
vector<int>A[MAXN];//每一列可以导致哪几行顺序
vector<int>B[MAXN];//每行顺序可以解锁哪几列的一层封锁
int ans[MAXN];
int n,m;
void sol(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
memset(lim,0,sizeof lim);
for(int i=1;i<n;i++)B[i].clear();
for(int i=1;i<=m;i++)A[i].clear();
for(int i=1;i<n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]<a[i+1][j]){
A[j].push_back(i);
}
else if(a[i][j]>a[i+1][j]){
B[i].push_back(j);
lim[j]++;
}
}
}
int cnt=0;
for(int t=1;t<=m;t++){
int p=0;
for(int i=1;i<=m;i++){
if(lim[i]==0){
p=i;
lim[i]=0x3f3f3f3f;
break;
}
}
if(p==0){
break;
}
ans[++cnt]=p;
for(auto i:A[p]){
for(auto j:B[i]){
lim[j]--;
}
B[i].clear();
}
A[p].clear();
}
if(cnt!=m){
cout<<"-1"<<endl;
}
else{
for(int i=1;i<=m;i++){
cout<<ans[i]<<" \n"[i==m];
}
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
while(cin>>n>>m){
sol();
}
}
I.2019
题意:
给出n个点的一个树,每条边都有一个权值,找出所有满足u~v路径上权值和为2019倍数的点对(无序)
思路:
队里的OI✌:这不一眼淀粉质吗...但是由于我们两个都不会点分治,在py大大的现学现用下没过,于是我就树上dp试着搞了搞(最后十分钟过了!我靠!)
用dp[i][j]表示以i为根的子树中,距离i长度为j(mod 2019)的点的个数
dp[i][0]初始化为1,因为对于一个点到其本身距离为0
然后跑dfs就好了
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN=2e4+10;
const int mod=2019;
const int inf=1e18;
vector<pair<int,int> >e[MAXN];
int dp[MAXN][mod];
int n,ans;
void dfs(int pos,int fa){
dp[pos][0]=1;
for(auto i:e[pos]){
int v=i.first,w=i.second;
if(v==fa)continue;
dfs(v,pos);
for(int j=0;j<=2018;j++){
ans+=dp[v][j]*dp[pos][((2019-w-j)%2019+2019)%2019];//距离u为j,加上uv边,还差距离v点2019-w-j的
}
for(int j=0;j<=2018;j++){
dp[pos][(j+w)%2019]+=dp[v][j];//更新一下距离根为各个模数的条数
}
}
}
void sol(){
ans=0;
for(int i=1;i<=n;i++){
e[i].clear();
for(int j=0;j<=2018;j++){
dp[i][j]=0;
}
}
int u,v,w;
for(int i=1;i<n;i++){
cin>>u>>v>>w;
e[u].push_back({v,w});//加边
e[v].push_back({u,w});//加边
}
dfs(1,-1);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
// cin>>t;
// t=1;
while(cin>>n){
sol();
}
return 0;
}
K.双向链表练习题
题意:
模拟两步操作:把ab链表连接起来,然后进行翻转,赋值给a,同时将b链表清空
思路:
STL是个好东西
因为是A链表和B链表连接起来再翻转,发现这不就是相当于把B链表和A链表的反表连接起来了嘛,所以那就直接正反都存一下好了
code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
int n,m;
list<int>lst[MAXN],rlst[MAXN];
void sol(){
for(int i=0;i<=n;i++){
lst[i].clear();
rlst[i].clear();
}
for(int i=1;i<=n;i++){
lst[i].push_back(i);
rlst[i].push_back(i);
}
int a,b;
while(m--){
cin>>a>>b;
rlst[a].splice(rlst[a].begin(),rlst[b]);
lst[a].splice(lst[a].end(),lst[b]);
swap(lst[a],rlst[a]);
}
cout<<lst[1].size()<<' ';
for(auto i:lst[1]){
cout<<i<<' ';
}
cout<<endl;
}
signed main(){
while(cin>>n>>m){
sol();
}
}