//
//  main.cpp
//  競技20190629

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <algorithm>
#include <cmath>
#include <vector>
#include <regex>
#include <map>
#include <set>
using namespace std;
#define ll long long int
#define FOR(i,min,max) for(int i=(min);i<(max);i++)
#define FOReq(i,min,max) for(int i=(min);i<=(max);i++)
#define FORREV(i,max,min) for(int i=(max)-1;i>=(min);i--)
const int  MOD=1000000007;
const int  INF=2000000000;
const double  Micro=1e-5;

int noterror(){
    if(INF || Micro || MOD){;}
    return 0;
}

#define pf printf
#define sf scanf
#define in(x) {cin >> (x);}
#define inarray(array,min,max) FOR(i,min,max){in(array[i]);}
#define inarrayW(arrayA,arrayB,min,max) FOR(i,min,max){in(arrayA[i]);in(arrayB[i]);}




int br(){
    printf("\n");
    return 0;
}
ll combi(int n, int k) {
    if (n == k || k == 0 )
        return 1;
    else {
        return combi(n - 1, k - 1) + combi(n - 1, k);
    }
}
ll modinv(long long a, long long m) {
    long long b = m, u = 1, v = 0;
    while (b) {
        long long t = a / b;
        a -= t * b; swap(a, b);
        u -= t * v; swap(u, v);
    }
    u %= m;
    if (u < 0) u += m;
    return u;
}
ll combi2(ll n,ll k,ll m){
    ll ans=1;
    for(ll i=0;i<k;i++){
        ans=(ans*(n-i))%m;
        ans=(ans*modinv(i+1,m))%m;
    }
    return ans;
}

int gcd(int a,int b){//最大公約数
    if(b==0){
        return a;
    }else{
        return gcd(b,a%b);
    }
}

int seiki()//正規表現
{
    string S;
    cin >> S;
    
    regex re("[dream|dreamer|erase|eraser]*");
    
    if(regex_match(S,re)){
        cout << "YES" << endl;
    }else{
        cout << "NO" << endl;
        
    }
    return 0;
}

//sort(a,a+100,greater<int>());大きい順
//sort(a,a+100);小さい順
/*
 int N;
 cin >> N;
 vector<int> d(N);
 int i = 0;
 int n = N;
 while(n--){
 cin >> d[i];
 i++;
 }
 
 sort(d.begin(),d.end());
 d.erase( unique(d.begin(),d.end()), d.end());隣り合った重複を削除し、消した分要素数を詰める
 cout<<d.size()<<endl;
 return 0;
*/
/*
 ペアの使い方
 vector<pair<long long,long long>> arr(N);
 for(int i=0;i<N;i++){
 cin >> arr[i].first >> arr[i].second;
 }
 sort(arr.begin(),arr.end());
 */
/*
int X(){ //stringの偶数文字目だけ出力
    string s;
    cin >> s;
    size_t size = s.length();
    
    string ans;
    
    for(int i=0;i<size;i++){
        if(i%2==0){
            ans = ans+s[i];
            
        }
    }
    cout << ans << endl;
    
    return 0;
}


int Ap0629(){ //AABBかABBAかABABなのかどうか
    string s;
    cin >> s;
    if(s[0]==s[1] && s[1]==s[2] && s[2]==s[3]){return printf("No\n");}
    if(s[0]==s[1] && s[2]==s[3]){return printf("Yes\n");}
     if(s[0]==s[2] && s[1]==s[3]){return printf("Yes\n");}
     if(s[0]==s[3] && s[1]==s[2]){return printf("Yes\n");}
  return printf("No\n");
}

int mymid(int a,int b,int c){
    if(a>=b && b>=c )return b;
    if(a<=b && b<=c )return b;
    return -1;
}

int Bp0629(){//連続三項で中間の値になるものの数
    int N;
    cin >> N;
    int p[N];
    for(int i=0;i<N;i++){cin >> p[i];}
    int ans=0;
    for(int i=1;i<N-1;i++){
        if(mymid(p[i-1],p[i],p[i+1])==p[i])ans++;
    }
    printf("%d\n",ans);
    return 0;
}

int Cp0629(){//数列の中央値を求める感じで
    int N;
    cin >> N;
    vector<int> d(N);
    int i = 0;
    int nn = N;
    while(nn--){
        cin >> d[i];
        i++;
    }
    sort(d.begin(),d.end());
    printf("%d\n",d[N/2]-d[N/2-1]);
    return 0;
}


int Dp0629(){//高校数学の組み合わせ論とmod 10e9+7でのnCrを求められるか
    ll n,k;
    cin >> n >> k;
    //for(int i=1;i<=k;i++){printf("%d\n",(int)(combi2(n-k+1,i)*combi(k-1,i-1)) );}
    for(int i=1;i<=k;i++){printf("%lld\n",(combi2(n-k+1,i,MOD)*combi2(k-1,i-1,MOD))%MOD );}
    return 0;
}
*/
struct UnionFind {
    vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2
    
    UnionFind(int N) : par(N) { //最初は全てが根であるとして初期化
        for(int i = 0; i < N; i++) par[i] = i;
    }
    
    int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
        if (par[x] == x) return x;
        return par[x] = root(par[x]);
    }
    
    void unite(int x, int y) { // xとyの木を併合
        int rx = root(x); //xの根をrx
        int ry = root(y); //yの根をry
        if (rx == ry) return; //xとyの根が同じ(=同じ木にある)時はそのまま
        par[rx] = ry; //xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
    }
    
    bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
        int rx = root(x);
        int ry = root(y);
        return rx == ry;
    }
};
int UniFi() {
    int N, M;
    cin >> N >> M;
    vector<int> P(N);
    for(int i = 0; i < N; i++){
        cin >> P[i];
    }
    
    UnionFind tree(N);//木を初期化
    
    for(int i = 0; i < M; i++) {
        int x, y;
        cin >> x >> y;
        tree.unite(x-1, y-1); // x-1の木とy-1の木を併合する
    }
    
    int count = 0;
    for(int i = 0; i < N; i++) {
        if (tree.same(i, P[i]-1)) { //iとP[i]-1が同じ木に属するなら，カウンタに1追加
            count++;
        }
    }
    cout << count << endl;
    return 0;
}






int nap(){//ナップサック問題
    int N,W;
    in(N);in(W);
    int v[N],Weight[N];
    inarrayW(v, Weight, 0, N)
    int DP[N+1][W+1];
    FOR(i, 0, W+1){DP[0][i]=0;}

    FOR(i,0, N){//i番目までの品物で
        FOR(w,0,W+1){//重さがW以下
            if(w<Weight[i]){DP[i+1][w]=DP[i][w];}
            else {DP[i+1][w]=max(DP[i][w],DP[i][ w-Weight[i] ]+v[i]);}
        }
    }
    FOR(i, 0, N+1){
        FOR(w,0,W+1){
            pf("%d,",DP[i][w]);
        }
        br();
    }

    pf("%d\n",DP[N][W]);
    return 0;
}


//#define int long long int



int tekitou(){
    ll a[100]={0,1,};
    pf("0,1,");
    FOR(i, 2, 50){
        a[i]=a[i-2]+a[i-1];
        pf("%lld,",a[i]);
    }
    return 0;
}
        
        int tatakidai(){
            int A=0;int B=0;int C=0;A=B=C;
            in(A);//in(B);in(C);
            string s;
            in(s);
            
            int array1[A];
            inarray(array1, 0, A);
            
            int array2[B],array3[B];
            inarrayW(array2, array3, 0, B);
            
            int ans[B];
            FOR(i, 0, B){pf("%d",ans[i]);}
            
            return 0;
        }

int Ap(){
    int n,a,b;
    in(n);in(a);in(b);
    pf("%d\n",min(n * a,b));
    
    return 0;
}
int sq(int X){
    int a;
    a = (int)sqrt((double)X); /* Nの平方根を求めint型に格納することで小数部分を捨てる。 */
    if (a*a == X) { /* a^2 と nが等しければ平方数と判断する */
        return 1;
    }
    return 0;
}
        
int Bp(){
    int n,d;
    in(n);in(d);
    int X[n][d];
    FOR(i, 0, n){
        FOR(k, 0, d){
            in(X[i][k]);
        }
    }
    int ans=0;
    int distance2=0;
    FOR(i, 0, n){
        FOR(k, i+1, n){
            distance2=0;
            FOR(point,0,d){
                distance2+=(X[i][point]-X[k][point])*(X[i][point]-X[k][point]);
            }
            if(sq(distance2)){ans++;}
        }
    }
    pf("%d\n",ans);
    return 0;
}
int Cp(){
    return 0;
}
int Dp(){
    return 0;
}
int Ep(){
    return 0;
}
int Fp(){
    return 0;
}
signed main(){
    //tekitou();
    //Ap();
    Bp();
    //Cp();
    //Dp();
    //Ep();
    //Fp();
    return 0;
}


Measured memory_bytes: 256